この記事は公開されてから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
- 新しいワーカーノードグループへの移行
- Migrating to a New Worker Node Group - Amazon EKS
- 既存のワーカーノードグループの更新
- Updating an Existing Worker Node Group - 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有り無し混じって汚いのはごめんなさい...許してください...
以上でした!!