kopsで構築したkubernetesクラスタを1.11.7->1.13.10までバージョンアップした

タイトルの通りです. 結構ハマったので書き残しておく.

前提条件

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 というエラーが出ている.

これに近いログは,一応調べると出てきた.

dev.to

www.oipapio.com

どうやら,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になるらしい.

そりゃうまく行かなそうだぞ!!!

そこでこういうのを見つける.

icicimov.github.io

ちなみに公式にもあった.

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になっている必要がある.このために,

  1. etcd2 + Legacyのまま1.12系に上げる
  2. etcd2 + Managerに変更する
  3. 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 clusterkops rolling-update したところ,無事失敗した.またかよ!!

また etcdが起動できないってエラーが出てるよ…….

仕切り直してクラスタを作り直す

これは流石に最終手段にしたかったのだが,クラスタを作り直した.さすがにちゃんと順を踏んでバージョンアップしようかなぁと思っていたのだけれど,そもそもそろそろgossip-basedなクラスタではなく,ちゃんとドメイン取得しRoute53にレコード登録してクラスタを作成したかった.

これは経済的な理由がある.

gossip-basedなクラスタはRoute53にレコードを作成しない.となると,kubectlがアクセスするAPIエンドポイントはどこになるのか? これは作成してみればわかるが, gossip-basedなkubernetesクラスタは,APIエンドポイントのためにELBを一つ立ち上げる.そしてそのELBのDNSAPIのエンドポイントになっているのだ.

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が変わっていたとしても,変更に追従できていないのは納得できる.

これに関しては,まとまったドキュメントが見当たらなかったのだが,

https://github.com/kubernetes/kops/blob/0d1706fb9d3fd8f84bca64662118f876241c01b7/pkg/model/iam/iam_builder.go

ソースを当たれば何を当てたら良いかわかる.

  • 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が使えるようになった,はず.

github.com

これはまだやっていないのだが,LaunchConfigurationの場合,インスタンスにSpot指定をしてSpotInstanceをASG内で起動しても,SpotのValid Untilがデフォルトの7日から変更できずに,7日経つとSpotではなくなってしまうという状態だった.

github.com

これを回避するために家のクラスタでは,一週間に1回,クラスタ内の全インスタンスを洗い替えている. のだが,LaunchTemplateを利用すればValid Untilを書き換えられずはず(これはまだ試してないので確証はない).