EKSを1.10から1.11にアップデートをしてみた

EKSで1.10から1.11のアップデートを試してみました。
2018.12.22

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

おはようございます、加藤です。 EKSのアップデートを試してみました。現在、EKSが対応しているk8sのバージョンは下記の通りです。

  • 1.10.3
  • 1.11.5

1.10から1.11へのアップデートを試してみます!

やってみた

クラスタの更新

クラスタの更新はマネジメントコンソールとAWS CLIから行うことが可能です。 今回はCLIからアップデートします。

環境変数に必要なパラメータをセットします。 EKSが使用するVPCとControl Plane(マスターノード)が使用するセキュリティグループをCFnで作成しているのでOutputから取得します。 もちろん、マネジメントコンソールで調べて設定してもOKです。

# クラスター一覧を確認
aws eks list-clusters | jq -r '.clusters[]'

# 変数にクラスター名とバージョンををセット(環境に合わせて変更してください)
export K8S_CLUSTER_NAME=eks-dev-Cluster
export K8S_VERSION='1.11'

export UPDATE_ID=$( \
aws eks update-cluster-version \
--name ${K8S_CLUSTER_NAME} \
--kubernetes-version ${K8S_VERSION} \
| jq -r '.update.id')

# アップデートステータスがSuccessfulになるのを待つ
aws eks describe-update \
--name ${K8S_CLUSTER_NAME} \
--update-id ${UPDATE_ID} \
| jq -r '.update.status'

kube-proxyがk8sの1.11.5に対応するイメージを使用するようにdaemonsetにパッチを適用します。

# k8sのバージョンを取得する
kubectl version -o json | jq -r '.serverVersion.gitVersion'
# v1.11.5-eks-6bad6d → v1.11.5と結果を読み替える 
kubectl patch daemonset kube-proxy \
-n kube-system \
-p '{"spec": {"template": {"spec": {"containers": [{"image": "602401143452.dkr.ecr.ap-northeast-1.amazonaws.com/eks/kube-proxy:v1.11.5","name":"kube-proxy"}]}}}}'

# パッチ内容を確認します
kubectl get daemonset kube-proxy -n kube-system -o wide
#NAME         DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE   CONTAINERS   IMAGES                                                                SELECTOR
#kube-proxy   3         3         3       2            3           <none>          1h    kube-proxy   602401143452.dkr.ecr.ap-northeast-1.amazonaws.com/eks/kube-proxy:v1.11.5   k8s-app=kube-proxy

k8s v1.11からはDNS・サービスディスカバリとしてCoreDNSがデフォルトです。変更したい場合は下記を参考にしてください。 Installing CoreDNS - Amazon EKS

ワーカーノードの更新

ワーカーノードの更新には、2つの方法があります。 Worker Node Updates - Amazon EKS

今回は「新しいワーカーノードグループへの移行」の方でやってみました。

CFnで新Worker Nodesを作成します。

template-eks-nodegroup.yaml

curl -O https://raw.githubusercontent.com/kmd2kmd/cfn-eks/master/template-eks-nodegroup.yaml

リージョンとバージョン毎のAMIは下記ページで確認できます。今回はv1.11でap-northeast-1なのでami-063650732b3e8b38cです。 Amazon EKS-Optimized AMI - Amazon EKS

環境変数をセットします。今回の環境ではVPCやクラスタのロールをCFnで作成していたのでスタックのOutputsから取得します。(スタック名:eks-dev-base)

export BASE_STACK_NAME='eks-dev-base'

export CONTROLLPLANE_SECURITYGROUP_IDS=$( \
aws cloudformation describe-stacks \
--stack-name ${BASE_STACK_NAME} | \
jq -r '.Stacks[].Outputs[] | select(.OutputKey == "SecurityGroups")' | \
jq -r '.OutputValue')

export VPC_ID=$( \
aws cloudformation describe-stacks \
--stack-name ${BASE_STACK_NAME} | \
jq -r '.Stacks[].Outputs[] | select(.OutputKey == "VpcId")' | \
jq -r '.OutputValue')

export SUBNET_IDS=$( \
aws cloudformation describe-stacks \
--stack-name ${BASE_STACK_NAME} | \
jq -r '.Stacks[].Outputs[] | select(.OutputKey == "SubnetIds")' | \
jq -r '.OutputValue')

newNodes="eks-dev-nodegroup-20181222-0"
export NODE_IMAGE_ID='ami-063650732b3e8b38c'
export KEY_NAME='cm-kato.ryo'
export NODE_VOLUME_SIZE='20'
export NODE_GROUP_NAME='eks-nodegroup-20181222-0'

パラメータファイルを生成します。

jo -a \
$(jo ParameterKey=KeyName ParameterValue=${KEY_NAME}) \
$(jo ParameterKey=NodeImageId ParameterValue=${NODE_IMAGE_ID}) \
$(jo ParameterKey=Subnets ParameterValue=${SUBNET_IDS}) \
$(jo ParameterKey=NodeVolumeSize -s ParameterValue=${NODE_VOLUME_SIZE}) \
$(jo ParameterKey=NodeGroupName ParameterValue=${NODE_GROUP_NAME}) \
$(jo ParameterKey=ClusterControlPlaneSecurityGroup ParameterValue=${CONTROLLPLANE_SECURITYGROUP_IDS}) \
$(jo ParameterKey=VpcId ParameterValue=${VPC_ID}) \
$(jo ParameterKey=ClusterName ParameterValue=${K8S_CLUSTER_NAME}) \
> parameter-eks-nodegroup.json

CFnを実行します。

aws cloudformation create-stack \
--stack-name ${NODEGROUP_STACK_NAME} \
--template-body=file://template-eks-nodegroup.yaml \
--parameters=file://parameter-eks-nodegroup.json  \
--capabilities CAPABILITY_IAM

aws cloudformation wait stack-create-complete \
--stack-name ${NODEGROUP_STACK_NAME}

export K8S_NODE_INSTANCE_ROLE=$( \
aws cloudformation describe-stacks \
--stack-name ${NODEGROUP_STACK_NAME} | \
jq -r '.Stacks[].Outputs[] | select(.OutputKey == "NodeInstanceRole")' | \
jq -r '.OutputValue')

echo "NodeInstanceRole"
echo ${K8S_NODE_INSTANCE_ROLE}

ノードのインスタンスロールをメモしておきます。

新旧のWoker NodesのSecurity Group IDを取得して、それぞれの間の通信を全許可します。

oldNodes="eks-dev-nodegroup-000"

oldSecGroup=$(aws cloudformation describe-stack-resources --stack-name $oldNodes \
--query 'StackResources[?ResourceType==`AWS::EC2::SecurityGroup`].PhysicalResourceId' \
--output text)
newSecGroup=$(aws cloudformation describe-stack-resources --stack-name $newNodes \
--query 'StackResources[?ResourceType==`AWS::EC2::SecurityGroup`].PhysicalResourceId' \
--output text)

aws ec2 authorize-security-group-ingress --group-id $oldSecGroup \
--source-group $newSecGroup --protocol -1
aws ec2 authorize-security-group-ingress --group-id $newSecGroup \
--source-group $oldSecGroup --protocol -1

mapRolesに新Worker NodesのインスタンスARNを追加します。

# 実行するとエディタが開く
kubectl edit configmap -n kube-system aws-auth

下記を参考にをメモしたインスタンスARNに書き換えてください。

apiVersion: v1
data:
  mapRoles: |
    - rolearn: <ARN of instance role (not instance profile)>
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes
    - rolearn: arn:aws:iam::111122223333:role/workers-1-10-NodeInstanceRole-U11V27W93CX5
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes

v1.11.5のNodesのSTATUSがReadyになることを確認します。

kubectl get nodes

旧Worker Nodesでリソースが新規作成されないように設定します。 今回はVERSIONが1.10.11であるものを旧Worker Nodesと判断しています。

K8S_VERSION=1.10.11
nodes=$(kubectl get nodes -o jsonpath="{.items[?(@.status.nodeInfo.kubeletVersion==\"v$K8S_VERSION\")].metadata.name}")
for node in ${nodes[@]}
do
    echo "Tainting $node"
    kubectl taint nodes $node key=value:NoSchedule
done

クラスターのDNSプロバイダ(今回の場合はkube-dns)のレプリカ数を2以上にします。現在1なので変更しました。

kubectl get deployments -l k8s-app=kube-dns -n kube-system
kubectl scale deployments/kube-dns --replicas=2 -n kube-system

# AVAILABLEが2になるのを待つ
kubectl get deployments -l k8s-app=kube-dns -n kube-system

旧Worker NodedのDrainを行います。

K8S_VERSION=1.10.11
nodes=$(kubectl get nodes -o jsonpath="{.items[?(@.status.nodeInfo.kubeletVersion==\"v$K8S_VERSION\")].metadata.name}")
for node in ${nodes[@]}
do
    echo "Draining $node"
    kubectl drain $node --ignore-daemonsets --delete-local-data
done

新旧Worker Nodes間の通信を許可していたSecurity Groupを削除します。

aws ec2 revoke-security-group-ingress --group-id $oldSecGroup \
--source-group $newSecGroup --protocol -1
aws ec2 revoke-security-group-ingress --group-id $newSecGroup \
--source-group $oldSecGroup --protocol -1

旧Worker Nodesを削除します。

aws cloudformation delete-stack \
--stack-name $oldNodes

aws cloudformation wait stack-delete-complete \
--stack-name $oldNodes

mapRolesから旧Worker NodesのインスタンスARNを削除します。

kubectl edit configmap -n kube-system aws-auth

下記を参考に旧Woker Nodesのエントリを削除します。

apiVersion: v1
data:
  mapRoles: |
    - rolearn: <新Worker Nodes ARN>
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes
    - rolearn: <旧Worker Nodes ARN>
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes

kube-dnsのレプリカ数を元の1に戻します。

kubectl scale deployments/kube-dns --replicas=1 -n kube-system

あとがき

無事にEKSのアップデートが行えました! 若干面倒ですが、一度理解してしまえば問題なさそうですね。また、このアップデート手順をCI/CDする仕組みを作るべきか考えてみましたが以下の理由で不要と思っています。

  • アップデート頻度が低い
  • k8sのアップデート頻度的に年数回
  • kube-dns → CoreDNSみたいな都度異なるプロセスが必要
  • 自動化しにくい

後、環境変数の定義がexport有り無し混じって汚いのはごめんなさい...許してください...

以上でした!!