kubesprayを使って、kubernetesのバージョンをv1.12.7から順番に上げていった時のログ

kubesprayはansibleを利用したkubernetes環境を構築するためのplaybookですが、制限事項としてバージョンアップをする場合に、カレントバーション"N"から、"N+1"へのバージョンアップしかサポートしていません。

つまり1歩、1歩進む必要があるのですが、kubespray v2.8.5(kubernetes v1.12.7)からバージョンアップするには、v2.9.0, v2.10.4 を経由する必要があり、これらのバージョンはあまり良くメンテナンスされていません。

最終的にv2.11.0を適用する作業のメモを残す事にしました。
なお、環境はUbuntu 18.04の最新版（現時点で18.04.3, 4.15.0-64）を利用しています。

## kubesprayの環境について

githubからcheckoutした公式リポジトリを使用して、該当のrefs/tags/_version_に対応するブランチを作成して作業しています。

このため、masterブランチでは変更は行なわず、ansible.cfgなどの修正はサブブランチ上で行なうようになっています。
別バージョンのブランチを作成する度に、ansible.cfgを更新する必要が発生するなど、面倒な点はあります。

kubesprayとkubernetesの両方のバージョンを扱うので、一覧を作成しておきます。

|kubespray|kubernetes|
|---------|----------|
|v2.8.5   |v1.12.7 |
|v2.9.0   |v1.13.5 |
|v2.10.4  |v1.14.6 |
|v2.11.0  |v1.15.3 |

特に断りがなければ、バージョンはkubesprayを指しています。

## 作業の基本的な手順

README.mdやdocs/upgrades.mdに記載されている手順に従って進めます。

docs/upgrades.mdにはcontrib/inventory_builder/inventory.pyを実行する手順などは掲載されていないので、checkoutしたら両方のファイルを確認して進めてください。

```bash:例
$ git checkout refs/tags/v2.9.0 my_v2.9.0
```

## from v2.8.5 to v2.9.0

作業前にansibleなどのバージョンを最新にしています。
念のため ~/.local/lib/python2.7/ ディレクトリは削除しています。

```bash
$ pip3 install -r requirements.txt
```

なお、ansible-playbookコマンドを実行する前に、```kubectl get node```で、各ノードが問題なくv1.13.5で稼動している事を確認しています。
NotReadyになっているノードは再起動していますが、問題ないノードは稼動したまま次のバージョンアップを実行しています。

### 問題点1
hosts.iniファイルを作るよう、README.mdに記述されていますが、hosts.yaml のようにsuffixを.yaml or .ymlにしないとエラーとなります。

```bash
$ mv inventory/mycluster/hosts.ini inventory/mycluster/hosts.yaml
```

### 問題点2

修正したhosts.yamlファイルを指定しても、別のエラー([issue#3985](https://github.com/kubernetes-sigs/kubespray/issues/3985))が発生します。

```bash
$ ansible-playbook upgrade-cluster.yml -b -i inventory/mycluster/hosts.yaml -e kube_version=v1.13.5

...
fatal: [node1]: FAILED! => {"reason": "'delegate_to' is not a valid attribute for a TaskInclude\n\nThe error appears to be in '/home/kubespray/roles/download/tasks/download_container.yml': line 2, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n---\n- name: container_download | Make download decision if pull is required by tag or sha256\n  ^ here\n"}
...
```

これを回避するため ansible.cfg の invalid_task_attribute_failed に、以前のデフォルト値である False を設定します。

```diff:ansible.cfg
diff --git a/ansible.cfg b/ansible.cfg
index bed2c4ae..6e0cbd57 100644
--- a/ansible.cfg
+++ b/ansible.cfg
@@ -3,6 +3,7 @@ pipelining=True
 ssh_args = -o ControlMaster=auto -o ControlPersist=30m -o ConnectionAttempts=100 -o UserKnownHostsFile=/dev/null
 #control_path = ~/.ssh/ansible-%%r@%%h:%%p
 [defaults]
+invalid_task_attribute_failed = False
 strategy_plugins = plugins/mitogen/ansible_mitogen/plugins/strategy
 
 host_key_checking=False
```

## from v2.9.0 to v2.10.4 

v2.9.0を実行する際に遭遇した問題には同様に対応し、pip3 installの実行、hosts.yamlの作成、ansible.cfgへのinvalid_task_attribute_failedの追記は同様に実施しています。

これまでのところ、これらの対応で問題なく、v2.10.4へのアップグレードは成功しています。

## from v2.10.4 to v2.11.0

前項と同様にv2.11.0へアップグレードしています。

ここでは一度、etcd : Configure | Check if etcd cluster is healthy タスクの実行に失敗しています。
手動で etcdctl ... cluster-healthy コマンドで確認すると、他のノードとの通信に失敗している様子だったので、全ノードを一度リスタートしています。

```bash
# etcdctl --endpoints=https://192.168.1.66:2379 --ca-file=/etc/kubernetes/ssl/etcd/ca.pem --cert-file=/etc/kubernetes/ssl/etcd/member-node2.pem --key-file=/etc/kubernetes/ssl/etcd/member-node2-key.pem cluster-healthy

member 6810c7d875b619f3 is healthy: got healthy result from https://192.168.1.66:2379
failed to check the health of member 7c1a3f8e165d33f9 on https://192.168.1.67:2379: Get https://192.168.1.67:2379/health: dial tcp 192.168.1.67:2379: connect: connection refused
member 7c1a3f8e165d33f9 is unreachable: [https://192.168.1.67:2379] are all unreachable
member ff525713c7d33c91 is healthy: got healthy result from https://192.168.1.65:2379
cluster is degraded
```

再度ansible-playbookコマンドを実行して、無事にアップデートに成功しています。

```bash
# kubectl get node
NAME       STATUS   ROLES         AGE    VERSION
node1   Ready    master,node   243d   v1.15.3
node2   Ready    master,node   243d   v1.15.3
node3   Ready    node          243d   v1.15.3

# kubectl version
Client Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.3", GitCommit:"2d3c76f9091b6bec110a5e63777c332469e0cba2", GitTreeState:"clean", BuildDate:"2019-08-19T11:05:50Z", GoVersion:"go1.12.9", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.3", GitCommit:"2d3c76f9091b6bec110a5e63777c332469e0cba2", GitTreeState:"clean", BuildDate:"2019-08-19T11:05:50Z", GoVersion:"go1.12.9", Compiler:"gc", Platform:"linux/amd64"}
```

# 他の環境で遭遇した問題

## Case#1 存在しない /tmp/releases ディレクトリ

kubespray v2.11.0 へのバージョンアップがうまく行かず、選択的に更新しようと --tags=master タグを付けて cluster.yml を実行した際に、次のようなメッセージが表示され停止しました。

```
...
fatal: [node1 -> 192.168.1.51]: FAILED! => {"changed": false, "cmd": "/usr/bin/rsync --delay-updates -F --archive --no-owner --no-group --out-format=<<CHANGED>>%i %n%L /tmp/releases/hyperkube-v1.15.3-amd64 /usr/local/bin/kubectl", "msg": "rsync: change_dir \"/tmp/releases\" failed: No such file or directory (2)\nrsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1196) [sender=3.1.2]\n", "rc": 23}
...
```

"/tmp/releases"は download task が実行していますが、これが呼ばれていなかったことが原因です。
そのため **--tags=download,master** を実行し、/tmp/releases/ ディレクトリを準備しています。

## Case#2 Server Versionが古いまま固定されている

試験的に利用していたk8sクラスターをkubespray v2.11.0まで最新にしたと思っていたところ、```kubectl version```の出力がとんでもない事になっていました。

```bash
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.3", GitCommit:"2d3c76f9091b6bec110a5e63777c332469e0cba2", GitTreeState:"clean", BuildDate:"2019-08-19T11:05:50Z", GoVersion:"go1.12.9", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"12", GitVersion:"v1.12.7", GitCommit:"6f482974b76db3f1e0f5d24605a9d1d38fad9a2b", GitTreeState:"clean", BuildDate:"2019-03-25T02:41:57Z", GoVersion:"go1.10.8", Compiler:"gc", Platform:"linux/amd64"}
```

このまま v2.11.0 で、upgrade-cluster.yml を指定すると次のようなメッセージが表示されます。

```
TASK [kubernetes/master : kubeadm | Upgrade first master] ****************************************************
Thursday 26 September 2019  05:21:14 +0000 (0:00:00.045)       0:21:08.276 ****
fatal: [node1]: FAILED! => {"changed": true, "cmd": ["timeout", "-k", "600s", "600s", "/usr/local/bin/kubeadm", "upgrade", "apply", "-y", "v1.15.3", "--config=/etc/kubernetes/kubeadm-config.yaml", "--ignore-preflight-errors=all", "--allow-experimental-upgrades", "--allow-release-candidate-upgrades", "--etcd-upgrade=false", "--force"], "delta": "0:00:00.090079", "end": "2019-09-26 05:21:14.721837", "failed_when_result": true, "msg": "non-zero return code", "rc": 1, "start": "2019-09-26 05:21:14.631758", "stderr": "\t[WARNING ControlPlaneNodesReady]: there are NotReady control-planes in the cluster: [node1 node2]\n[upgrade/version] FATAL: the --version argument is invalid due to these fatal errors:\n\n\t- Specified version to upgrade to \"v1.15.3\" is too high; kubeadm can upgrade only 1 minor version at a time\n\nPlease fix the misalignments highlighted above and try upgrading again", "stderr_lines": ["\t[WARNING ControlPlaneNodesReady]: there are NotReady control-planes in the cluster: [node1 node2]", "[upgrade/version] FATAL: the --version argument is invalid due to t
hese fatal errors:", "", "\t- Specified version to upgrade to \"v1.15.3\" is too high; kubeadm can upgrade only 1 minor version at a time", "", "Please fix the misalignments highlighted above and try upgrading again"], "stdout": "[upgrade/config] Making sure the configuration is correct:\n[preflight] Running pre-flight checks.\n[upgrade] Making sure the cluster is healthy:\n[upgrade/version] You have chosen to change the cluster version to \"v1.15.3\"\n[upgrade/versions] Cluster version: v1.12.7\n[upgrade/versions] kubeadm version: v1.15.3", "stdout_lines": ["[upgrade/config] Making sure the configuration is correct:", "[preflight] Running pre-flight checks.", "[upgrade] Making sure the cluster is healthy:", "[upgrade/version] You have chosen to change the cluster version to \"v1.15.3\"", "[upgrade/versions] Cluster version: v1.12.7", "[upgrade/versions] kubeadm version: v1.15.3"]}
```

直接的な原因は、masterノードの /etc/kubernetes/manifests/kube-*.yaml ファイルが更新されていないことですが、なぜうまく更新されていないのかは気がついていませんでした。

/etc/kubernetes/manifests/kube-*.yaml のバージョン番号を変更しましたが、他にも /etc/apt/sources.d/ が邪魔をしてaptがエラーを出したので削除したり、重複したエントリを削除するなどしましたが、最終的には一度reset.ymlを実行して、再度cluster.ymlを実行しました。

時間がなく最後まで原因を追求できず残念な結果になってしまいました。
少なくともアップグレードは1つずつ進めていく事が大切です。

## Case#3 volume-plugin-dir が設定できていない

Rookそのものは動いているのに、upgrade-cluster.yml を実行してから、PodからCeph StorageやShared Filesystemがマウントできない現象が再発しました。

```text:kubectl_describeのログ
  Warning  FailedMount  67s (x17 over 37m)  kubelet, node4  Unable to mount volumes for pod "issuer-84d59898cd-n5d97_dex(0881bb47-3b89-4854-b07f-a976f6f035de)": timeout expired waiting for volumes to attach or mount for pod "dex"/"issuer-84d59898cd-n5d97". list of unmounted volumes=[dex-data]. list of unattached volumes=[config dex-data default-token-s8tht]
```

例によって kubelet.env ファイルから --volume-plugin-dir の指定が抜けています。
release-2.10タグのコードでは修正されていたので油断しました。

v2.11.0のコードをみる限り、systemd用のkubelet.serviceの中では **KUBELET_VOLUME_PLUGIN** が定義されていますが、これを設定する仕組みはどこにも存在していないようです。

この問題は既にレポートされています。
See also: [issue#5122](https://github.com/kubernetes-sigs/kubespray/issues/5122)

例によって roles/kubernetes/node/templates/kubelet.env.v1beta1.j2 の中で KUBELET_VOLUME_PLUGIN を設定するコードを追加しました。

```diff
diff --git a/roles/kubernetes/node/templates/kubelet.env.v1beta1.j2 b/roles/kubernetes/node/templates/kubelet.env.v1beta1.j2
index ddf97819..571bc1f3 100644
--- a/roles/kubernetes/node/templates/kubelet.env.v1beta1.j2
+++ b/roles/kubernetes/node/templates/kubelet.env.v1beta1.j2
@@ -62,5 +62,8 @@ KUBELET_CLOUDPROVIDER="--cloud-provider=external --cloud-config={{ kube_config_d
 {% else %}
 KUBELET_CLOUDPROVIDER=""
 {% endif %}
+{% if kubelet_flexvolumes_plugins_dir is defined %}
+KUBELET_VOLUME_PLUGIN="--volume-plugin-dir={{ kubelet_flexvolumes_plugins_dir }}"
+{% endif %}
 
 PATH={{ bin_dir }}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
```

kubelet_flexvolumes_plugins_dir変数はdefaults/main.ymlの中で設定されているのでチェックは不要ですが、念のために付けておきました。

また inventory/mycluster/group_vars/k8s-cluster/k8s-cluster.yml にRookで指定している場所と同じディレクトリを指定しておきます。

```yaml
kubelet_flexvolumes_plugins_dir: /var/lib/kubelet/volume-plugins
```

既に修正パッチがApprove待ちになっているので、将来のrelease-2.11タグでは修正されているはずです。

# まとめ

kubesprayを使う上で問題になりそうなのは、いろいろカスタマイズしたk8nクラスターを安全にアップグレードできるのか、いまだに良く理解できていません。

基本的には各設定とk8nクラスターの動作は切り離されているので、hosts.yamlファイルでのホスト名の指定さえ統一しておけば、k8s-cluster.yamlファイルの設定を忘れても、アップグレード後に設定を変更するという流れで対応できるはずです。

慣れていないと、```-e kube_version=```に設定する値をkubesprayのバージョン番号だと勘違いしてしまうかもしれませんが、kubernetesのバージョンを一覧から対応するものに変更してください。

なお、README.mdにも、kubesprayに対応するkubernetesのバージョンが書かれています。