タイトルの通りです. 結構ハマったので書き残しておく.
前提条件
kopsを使ってAWS上にkubernetesクラスタを作っていた.このときは,gossip-basedなクラスタを作っていた.
また,全てをkopsのコマンドで生成しているわけではなく,
- VPC, Subnet, RouteTable
- IAM
- SecurityGroup
あたりはterraformで管理しており,kopsの定義で,作成済みのリソースのarnを指定するという方法で構築している.
1.11.7 -> 1.11.10へ
流石にいきなり1.13.xに上げるのはよろしくない.多分トラブルし,変更が多すぎて原因を探るのが大変そうな予感がする. というわけでまずは,1.11系の最新まであげよう.
基本的な手順はこちらに示されている.
https://github.com/kubernetes/kops/blob/master/docs/upgrade.md
まず,
$ kops edit cluster $CLUSTER_NAME
して,中身のバージョンを書き換える.
apiVersion: kops/v1alpha2 kind: Cluster spec: # ...中略 kubernetesVersion: 1.11.7 # <- これを1.11.10へ
そして,
$ kops update cluster --name $CLUSTER_NAME
する.すると,変更するリソース一覧が表示される.なお,この時点ではまだ実際に変更は行われておらず, --yes
を付けることでリソース変更が行われる.
ちなみ,IAMをterraformで管理している場合,それを触られたくないので,
$ kops update cluster --lifecycle-overrides IAMRole=ExistsAndWarnIfChanges,IAMRolePolicy=ExistsAndWarnIfChanges,IAMInstanceProfileRole=ExistsAndWarnIfChanges --name $CLUSTER_NAME --yes
としている.
これで,クラスタの情報が書き換わった.
ただし,稼働中のkubernetesクラスタはまだ変化はない.kopsは,updateコマンドで,Network, IAM, SG, ASG, LaunchConfiguration等の変更を行う.しかし,新しいLaunchConfigurationの内容をインスタンスに反映するためには,ASGのインスタンスを洗い替える必要がある.
これを行うために,
$ kops rolling-update cluster $CLUSTER_NAME
する.
なお,今回の経験から,アップグレードのようにMasterもNodeも全てを変える必要がある場合,まずはMasterからやるべきである.そして,結構Masterで失敗するので,とりあえずMasterを上げて,うまく行ったらNodeを上げるのが良い. Nodeで失敗するということは,あまりなかった.
$ kops rolling-update cluster $CLUSTER_NAME --instance-group-roles=Master --yes
これでMasterのインスタンスのみを洗い替える.
うまく行ったら,
$ kops rolling-update cluster $CLUSTER_NAME --instance-group-roles=Node --yes
としてNodeを洗い替える.
ちなみに,1.11.7 -> 1.11.10は,あっさりアップグレード成功した.特になんのトラブルもなく,平然とクラスタは起動した.
1.11.10 -> 1.12.10
こちらを,先ほどと同じ手順で行ったところ,大爆死した.
まず,kops update cluster
は問題なく成功する.
しかし,kops rolling-update cluster
をすると,1台目のMasterを殺した後,新しいMasterはいつまで待ってもクラスタにjoinしてこない.
エラーを確認する
新しく生まれたMasterにsshし, /var/log/cloud-init-output.log
を確認する.こちらでは問題なくkubeletを起動しているように見えた.
しかし,
/var/log/kube-controller-manager.log
/var/log/kube-proxy.log
/var/log/kube-scheduler.log
は全て以下のようなエラーで埋め尽くされた.
E0911 04:22:29.923682 1 leaderelection.go:270] error retrieving resource lock kube-system/kube-controller-manager: Get https://127.0.0.1/api/v1/namespaces/kube-system/endpoints/kube-controller-manager?timeout=10s: dial tcp 127.0.0.1:443: connect: connection refused
さらに,/var/log/kube-apiserver.log
には,以下のようなエラーが出ていた.
W0911 04:21:34.770585 1 admission.go:78] PersistentVolumeLabel admission controller is deprecated. Please remove this controller from your configuration files and scripts. I0911 04:21:34.770927 1 plugins.go:158] Loaded 10 mutating admission controller(s) successfully in the following order: NamespaceLifecycle,LimitRanger,ServiceAccount,NodeRestriction,TaintNodesByCondition,Priority,DefaultTolerationSeconds,PersistentVolumeLabel,DefaultStorageClass,MutatingAdmissionWebhook. I0911 04:21:34.770938 1 plugins.go:161] Loaded 6 validating admission controller(s) successfully in the following order: LimitRanger,ServiceAccount,Priority,PersistentVolumeClaimResize,ValidatingAdmissionWebhook,ResourceQuota. F0911 04:21:54.773980 1 storage_decorator.go:57] Unable to create storage backend: config (&{etcd3 /registry [https://127.0.0.1:4001] /etc/kubernetes/pki/kube-apiserver/etcd-client.key /etc/kubernetes/pki/kube-apiserver/etcd-client.crt /etc/kubernetes/pki/kube-apiserver/etcd-ca.crt true 0xc000527cb0 <nil> 5m0s 1m0s}), err (dial tcp 127.0.0.1:4001: connect: connection refused)
途中までは成功しているように見えるが, Unable to create storage backend: config (&{etcd3
というエラーが出ている.
これに近いログは,一応調べると出てきた.
どうやら,etcdに問題があることはわかった.
そのときの /var/log/etcd-events.log
は以下のような出力を繰り返していた.
2019-09-10 15:17:45.610931 I | etcdserver: setting up the initial cluster version to 3.2 2019-09-10 15:17:45.611877 I | etcdserver: published {Name:etcd-events-c ClientURLs:[https://etcd-events-c.internal.base-prd.cluster.k8s.local:3995]} to cluster 715bb031b20871cc 2019-09-10 15:17:45.611948 I | embed: ready to serve client requests 2019-09-10 15:17:45.612309 I | embed: serving client requests on [::]:3995 2019-09-10 15:17:45.614327 N | etcdserver/membership: set the initial cluster version to 3.2 2019-09-10 15:17:45.614447 I | etcdserver/api: enabled capabilities for version 3.2 WARNING: 2019/09/10 15:17:45 Failed to dial 0.0.0.0:3995: connection error: desc = "transport: authentication handshake failed: remote error: tls: bad certificate"; please retry. I0910 15:17:46.058474 3163 controller.go:173] starting controller iteration I0910 15:17:46.058536 3163 controller.go:198] we are not leader I0910 15:17:54.630735 3163 etcdserver.go:226] updating hosts: map[10.0.2.63:[etcd-events-a.internal.base-prd.cluster.k8s.local] 10.0.23.125:[etcd-events-c.internal.base-prd.cluster.k8s.local]] I0910 15:17:54.630783 3163 hosts.go:84] hosts update: primary=map[10.0.2.63:[etcd-events-a.internal.base-prd.cluster.k8s.local] 10.0.23.125:[etcd-events-c.internal.base-prd.cluster.k8s.local]], fallbacks=map[etcd-events-c.internal.base-prd.cluster.k8s.local:[10.0.23.125 10.0.23.125]], final=map[10.0.2.63:[etcd-events-a.internal.base-prd.cluster.k8s.local] 10.0.23.125:[etcd-events-c.internal.base-prd.cluster.k8s.local]] I0910 15:17:54.630864 3163 hosts.go:181] skipping update of unchanged /etc/hosts 2019-09-10 15:17:54.899475 W | rafthttp: lost the TCP streaming connection with peer 72f5740f2fc7cdd6 (stream MsgApp v2 reader) 2019-09-10 15:17:54.899544 E | rafthttp: failed to read 72f5740f2fc7cdd6 on stream MsgApp v2 (unexpected EOF) 2019-09-10 15:17:54.899554 I | rafthttp: peer 72f5740f2fc7cdd6 became inactive 2019-09-10 15:17:54.899568 W | rafthttp: lost the TCP streaming connection with peer 72f5740f2fc7cdd6 (stream Message reader) I0910 15:17:55.000952 3163 etcdserver.go:439] StopEtcd request: header:<leadership_token:"iho--0hZxIGmGYlB02vfog" cluster_name:"etcd-events" > I0910 15:17:55.001022 3163 etcdserver.go:453] Stopping etcd for stop request: header:<leadership_token:"iho--0hZxIGmGYlB02vfog" cluster_name:"etcd-events" > I0910 15:17:55.001036 3163 etcdserver.go:618] killing etcd with datadir /rootfs/mnt/master-vol-02a94ec50c8cf0e63/data/CDUOSLvle66gZfky6_SrnA I0910 15:17:55.001058 3163 etcdprocess.go:107] Waiting for etcd to exit I0910 15:17:55.101173 3163 etcdprocess.go:107] Waiting for etcd to exit I0910 15:17:55.101204 3163 etcdprocess.go:112] Exited etcd: signal: killed I0910 15:17:55.101339 3163 etcdserver.go:471] archiving etcd data directory /rootfs/mnt/master-vol-02a94ec50c8cf0e63/data/CDUOSLvle66gZfky6_SrnA -> /rootfs/mnt/master-vol-02a94ec50c8cf0e63/data-trashcan/CDUOSLvle66gZfky6_SrnA
どうも,etcdが起動できないようで,途中でkillされている.
etcdのバージョン変わるらしい
ここでようやくkopsのリリースノートを見る.
https://github.com/kubernetes/kops/blob/master/docs/releases/1.12-NOTES.md
Please back-up important data before upgrading, as the etcd2 to etcd3 migration is higher risk than most upgrades. The upgrade is disruptive to the masters, see notes above.
超重要なことが書いてあった.
どうやら,etcdがetcd2 -> etcd3になるらしい.
そりゃうまく行かなそうだぞ!!!
そこでこういうのを見つける.
ちなみに公式にもあった.
https://github.com/kubernetes/kops/blob/master/docs/etcd3-migration.md
etcdを2に固定して1.12.10へ上げる(失敗
まず,kops edit cluster
して,
apiVersion: kops/v1alpha2 kind: Cluster spec: # 省略 etcdClusters: - cpuRequest: 200m etcdMembers: - instanceGroup: master-ap-northeast-1a name: a - instanceGroup: master-ap-northeast-1c name: c - instanceGroup: master-ap-northeast-1d name: d memoryRequest: 100Mi name: main version: 2.2.1 # <- これを追加 - cpuRequest: 100m etcdMembers: - instanceGroup: master-ap-northeast-1a name: a - instanceGroup: master-ap-northeast-1c name: c - instanceGroup: master-ap-northeast-1d name: d memoryRequest: 100Mi name: events version: 2.2.1 # <- これを追加 # 省略 kubernetes: 1.12.10 # <- 上げる
こうしてバージョン指定をした上で,kubernetesのバージョンを上げる.
これで, kops update cluster
して, kops rolling-update
してみた.
しかし,先程とまったく同じ現象に出くわし,1台目のMasterがクラスタに入れない.
etcdを2に固定し,Legacy指定のまま1.12.10へ上げる
先ほどと同じく,etcdは2系に固定し,さらに provider: Legacy
を指定する.
実は,kopsのetcdは,2->3に上がる際に,etcd-managerというものに変化している.これを変えた背景は,公式でも案内されている.
https://github.com/kubernetes/kops/blob/master/docs/etcd3-migration.md#background-info
Kubernetes is moving from etcd2 to etcd3, which is an upgrade that involves Kubernetes API Server downtime. Technically there is no usable upgrade path from etcd2 to etcd3 that supports HA scenarios, but kops has enabled it using etcd-manager.
にあるとおり,単にetcdを2->3に上げると,ダウンタイムが発生してしまう.このダウンタイムをなくすために,etcd-managerを使うとしている.
つまり,etcd2 -> etcd3に上げる際には,etcd-managerになっている必要がある.このために,
- etcd2 + Legacyのまま1.12系に上げる
- etcd2 + Managerに変更する
- etcd3にアップデート
という順序を辿る必要がある.
なので,先程の修正に加えて,
apiVersion: kops/v1alpha2 kind: Cluster spec: # 省略 etcdClusters: - cpuRequest: 200m etcdMembers: - instanceGroup: master-ap-northeast-1a name: a - instanceGroup: master-ap-northeast-1c name: c - instanceGroup: master-ap-northeast-1d name: d memoryRequest: 100Mi name: main provider: Legacy # <- これも追加 version: 2.2.1 # <- これを追加 - cpuRequest: 100m etcdMembers: - instanceGroup: master-ap-northeast-1a name: a - instanceGroup: master-ap-northeast-1c name: c - instanceGroup: master-ap-northeast-1d name: d memoryRequest: 100Mi name: events provider: Legacy # <- これも追加 version: 2.2.1 # <- これを追加 # 省略 kubernetes: 1.12.10 # <- 上げる
としてやる.
これで kops update cluster
し, kops rolling-update
したところ,無事1.12.10のMasterインスタンスが起動しKubernetesクラスタにjoinすることができた.
このままNodeも一度1.12系にあげてしまう.
etcdをManager指定にする
ではいよいよetcdを2系のままManager指定する.
apiVersion: kops/v1alpha2 kind: Cluster spec: # 省略 etcdClusters: - cpuRequest: 200m etcdMembers: - instanceGroup: master-ap-northeast-1a name: a - instanceGroup: master-ap-northeast-1c name: c - instanceGroup: master-ap-northeast-1d name: d memoryRequest: 100Mi name: main provider: Master # <- Masterに変更 version: 2.2.1 - cpuRequest: 100m etcdMembers: - instanceGroup: master-ap-northeast-1a name: a - instanceGroup: master-ap-northeast-1c name: c - instanceGroup: master-ap-northeast-1d name: d memoryRequest: 100Mi name: events provider: Master # <- Masterに変更 version: 2.2.1 # 省略 kubernetes: 1.12.10
これで kops update cluster
と kops rolling-update
したところ,無事失敗した.またかよ!!
また etcdが起動できないってエラーが出てるよ…….
仕切り直してクラスタを作り直す
これは流石に最終手段にしたかったのだが,クラスタを作り直した.さすがにちゃんと順を踏んでバージョンアップしようかなぁと思っていたのだけれど,そもそもそろそろgossip-basedなクラスタではなく,ちゃんとドメイン取得しRoute53にレコード登録してクラスタを作成したかった.
これは経済的な理由がある.
gossip-basedなクラスタはRoute53にレコードを作成しない.となると,kubectlがアクセスするAPIエンドポイントはどこになるのか? これは作成してみればわかるが, gossip-basedなkubernetesクラスタは,APIエンドポイントのためにELBを一つ立ち上げる.そしてそのELBのDNSがAPIのエンドポイントになっているのだ.
ELBは,起動しているだけでも$20/monthくらいの料金がかかる.プラスデータ転送料だが,kubectlだけでそこまで膨大なデータ転送はしないだろう.にしても $20/monthは結構でかい.t3.small一台追加できるくらいはある.
しかし,Route53にレコード登録さえできれば,このELBは不要になる. 即ち,kopsのクラスタ定義の
apiVersion: kops/v1alpha2 kind: Cluster spec: api: loadBalancer: type: Public
これを,
apiVersion: kops/v1alpha2 kind: Cluster spec: api: dns: {}
こうしてやり,ちゃんとドメインを取得しRoute53にHostZone登録さえしておけば,こちらの方が$20/month安くなる.
厳密にはドメイン料金がかかるが,$20/monthに比べたら全然安い値段で取れる.
というわけなので,いずれにしろクラスタは再作成したかったのである.
gossip-basedなクラスタは, **.k8s.local
という名前になってしまっており,これを **.example.com
に名前変更するため,これはクラスタごと作り直すレベルだ.
というわけで,どうせ作り直すなら,行き詰まってる今が良い.
1.13.10でクラスタを作成する
1.12.10のクラスタとは別に1.13.10のクラスタを作る.これは新規作成なので特に気を使うことなく kops create
すれば良いはず.
ただし,Route53を利用するので,ドメインをHostZoneに登録し,kops create --dns-zone ZONE_ID
という指定をしてやる必要がある.
なお,PrivateDNSも利用できるのだが,そもそもPrivateDNSを定義してしまうと,VPC内からしか名前解決できない. 家のネットワークがVPCとNATしてあったりすればそれでもいいのだが,そんなことはしていないので仕方なくPublicDNSを使った.
またetcdが起動しない
これは流石に起動するやろーと思っていたら,また起動しない.また Unable to create storage backend
だし,etcdが起動していないのか…….
と思って, /var/log/etcd-events.log
を見に行くと,内容が変わっている.
どうやら起動後,他のetcdと通信した結果を,s3に保存している.そこで
An error occurred (AccessDenied) when calling the PutObject operation: Access Denied
となっている. これはもしや権限が足らないだけなのでは?
IAM Policyを追加する
そういえば,MasterのIAM Roleはterraformで作成したものであり,kopsで管理していない.ということはkopsのバージョンアップに伴い必要なIAM Roleが変わっていたとしても,変更に追従できていないのは納得できる.
これに関しては,まとまったドキュメントが見当たらなかったのだが,
ソースを当たれば何を当てたら良いかわかる.
- s3:GetObject
- s3:DeleteObject
- s3:PutObject
は必要なのでこれを追加した. この他にも追従できていないIAM Policyを一気に追加した.
これを追加したところ,起動中だったインスタンスは無事クラスタにjoinし,1.13.10のkubernetesクラスタが全台起動した.
kubernetes上で動かすアプリケーションの定義は全てあるので,それをそのままapplyして周り,最後にDNSの向き先を変更しクラスタの移行は完了した.
まとめ
本当は連続的にバージョンアップをしたかったのだが,結果的に作り直しになってしまった.ELBの分が節約できたので良かったけど.
1.12.10での,Legacy -> Master変更時のetcd-events.logは,残念ながら記録していなかったので,正確にどのようなエラーがでていたかは確認できない. ただし,IAM Policyでエラーが出ていた可能性は非常に高く,もしかしたら,IAM Policyを追加していくだけでLegacy -> Masterの変更は成功していたかもしれない.
これが成功していれば1.12.10 -> 1.13.10も試せたのだが…….
また,kopsのCLI自体のバージョンも上げたので,LaunchConfigurationだけでなくLaunchTemplateが使えるようになった,はず.
これはまだやっていないのだが,LaunchConfigurationの場合,インスタンスにSpot指定をしてSpotInstanceをASG内で起動しても,SpotのValid Untilがデフォルトの7日から変更できずに,7日経つとSpotではなくなってしまうという状態だった.
これを回避するために家のクラスタでは,一週間に1回,クラスタ内の全インスタンスを洗い替えている. のだが,LaunchTemplateを利用すればValid Untilを書き換えられずはず(これはまだ試してないので確証はない).