Kubespray で OpenStack 上に Kubernetes クラスタをデプロイする

Terraform と Kubespray (Kargo) を使って OpenStack 環境上に Kubernetes クラスタをデプロイしてみました。 クラスタの初期構築を行い、スケールアウト、 Kubernetes 自体のアップデートを行う手順を社内向けドキュメントとして書いていたので、ブログ用に手直しして紹介したいとおもいます。

Kubespray の概要

Kubespray は Kubernetes クラスタをデプロイするための Ansible Playbooks です。各種クラウド環境にも対応しており、 AWS や GCP 等に対応しています。 元々 Kargo という名前だったようですが、 Kubespray に変わったようです。

repository はここ 👇

GitHub - kubernetes-sigs/kubespray: Deploy a Production Ready Kubernetes Cluster

Deploy a Production Ready Kubernetes Cluster. Contribute to kubernetes-sigs/kubespray development by creating an account on GitHub.

今回は kubespray-cli などのツールは使わず、 Terraform で構築したインスタンスに対して Kubespray の Ansible Playbook を流すという方法で試してみました。

手順は以下のサイトを参考にしています。

Deploy Kubernetes w/ Ansible & Terraform

Thoughts and tutorials on Linux, Cloud, and Automation.

Kubespray 実行環境の構築

GitHub - kubernetes-sigs/kubespray: Deploy a Production Ready Kubernetes Cluster

Deploy a Production Ready Kubernetes Cluster. Contribute to kubernetes-sigs/kubespray development by creating an account on GitHub.

まずは Ansible 2.3 系, Jinja2 2.9 系をインストール。

$ pip install ansible==2.3
$ pip install Jinja2==2.9

Terraform は tfenv を使っていたので、それを使用してインストールしました。

$ tfenv install 0.9.11
$ tfenv use 0.9.11
$ terraform version
Terraform v0.9.11

Kubernetes Cluster の初期構築

プロジェクトディレクトリの準備

適当な作業用ディレクトリをつくり、その中に kubespray のリポジトリを clone する。 参考にしたサイトと同様に terra-spray ディレクトリを作り、作業ディレクトリとしてみました。

$ mkdir terra-spray
$ cd terra-spray
$ git clone https://github.com/kubernetes-incubator/kubespray.git
Cloning into 'kubespray'...
remote: Counting objects: 14268, done.
remote: Total 14268 (delta 0), reused 0 (delta 0), pack-reused 14268
Receiving objects: 100% (14268/14268), 5.65 MiB | 474.00 KiB/s, done.
Resolving deltas: 100% (7614/7614), done.

OpenStack 上にインスタンスをつくろう

Terraform を実行する前に、 OpenStack の認証情報等を環境変数として持たせておきます。OpenStack RC ファイルを使うのが良いかと思います。

  • OS_AUTH_URL
  • OS_TENANT_ID
  • OS_TENANT_NAME
  • OS_PROJECT_NAME
  • OS_USERNAME
  • OS_PASSWORD
  • OS_REGION_NAME

Terraform テンプレートの作成

先程作成したプロジェクトディレクトリ配下に、以下の内容で Terraform の tf ファイルを作成する。

terra-spray
  ├── variables.tf            # 変数達
  ├── 00-k8s-nodes.tf         # VM を作成するためのファイル
  ├── 01-create-inventory.tf  # Ansible Inventory を生成するファイル
  └── kubespray               # git clone した kubespray
variables.tf
variable "availability_zones" {
  default = ["az01", "az02", "az03"]
}

variable "flavor_name" {
  default = "tiny"
}

variable "base_image" {
  default = "centos72"
}

variable "base_vlan" {
  default = "provider-2xxx"
}
00-k8s-nodes.tf

k8s の master, node 及び etcd 用インスタンスを作成します。kube-node kube-master etcd はそれぞれ別の VM にインストールました。2 master, 3 etcd, 3 node の構成です。

(ドキュメントにも書いてありますが、 etcd サーバはフェイルオーバのために少なくとも3台のサーバが必要となります。)

resource "openstack_compute_instance_v2" "k8s-master" {
  name         = "k8s-master${format("%02d", count.index+1)}"
  count        = "2"
  image_name   = "${var.base_image}"
  flavor_name  = "${var.flavor_name}"
  config_drive = false
  availability_zone = "${element(var.availability_zones, count.index)}"

  security_groups   = ["default"]

  network {
    name  = "${var.base_vlan}"
  }
}

resource "openstack_compute_instance_v2" "k8s-etcd" {
  name         = "k8s-etcd${format("%02d", count.index+1)}"
  count        = "3"
  image_name   = "${var.base_image}"
  flavor_name  = "${var.flavor_name}"
  config_drive = false
  availability_zone = "${element(var.availability_zones, count.index)}"

  security_groups   = ["default"]

  network {
    name  = "${var.base_vlan}"
  }
}

resource "openstack_compute_instance_v2" "k8s-node" {
  name         = "k8s-node${format("%02d", count.index+1)}"
  count        = "3"
  image_name   = "${var.base_image}"
  flavor_name  = "${var.flavor_name}"
  config_drive = false
  availability_zone = "${element(var.availability_zones, count.index)}"

  security_groups   = ["default"]

  network {
    name  = "${var.base_vlan}"
  }
}
01-create-inventory.tf

kubespray/inventory/inventoy に、Ansible の inventory を生成します。inventory に記述する必要がある group は kube-node kube-master etcd の3種類。

resource "null_resource" "ansible-provision" {
  depends_on = ["openstack_compute_instance_v2.k8s-master", "openstack_compute_instance_v2.k8s-node", "openstack_compute_instance_v2.k8s-etcd"]

  ## Create Masters Inventory
  provisioner "local-exec" {
    command =  "echo \"[kube-master]\" >> kubespray/inventory/inventory"
  }

  provisioner "local-exec" {
    command =  "echo \"${join("\n",formatlist("%s ansible_ssh_host=%s", openstack_compute_instance_v2.k8s-master.*.name, openstack_compute_instance_v2.k8s-master.*.network.0.fixed_ip_v4))}\" >> kubespray/inventory/inventory"
  }

  ## Create ETCD Inventory
  provisioner "local-exec" {
    command =  "echo \"\n[etcd]\n${join("\n",formatlist("%s ansible_ssh_host=%s", openstack_compute_instance_v2.k8s-etcd.*.name, openstack_compute_instance_v2.k8s-etcd.*.network.0.fixed_ip_v4))}\" >> kubespray/inventory/inventory"
  }

  ## Create Nodes Inventory
  provisioner "local-exec" {
    command =  "echo \"\n[kube-node]\" >> kubespray/inventory/inventory"
  }

  provisioner "local-exec" {
    command =  "echo \"${join("\n",formatlist("%s ansible_ssh_host=%s", openstack_compute_instance_v2.k8s-node.*.name, openstack_compute_instance_v2.k8s-node.*.network.0.fixed_ip_v4))}\" >> kubespray/inventory/inventory"
  }

  provisioner "local-exec" {
    command =  "echo \"\n[k8s-cluster:children]\nkube-node\nkube-master\" >> kubespray/inventory/inventory"
  }
}

terraform apply する

テンプレートが完成したら、terraform apply で VM を作成する。 apply が完了したら、 kubespray/inventory/inventory ファイルに正しく inventory が書かれているかどうか確認しておこう。

$ cat kubespray/inventory/inventory
[kube-master]
k8s-master01 ansible_ssh_host=192.0.2.1
k8s-master02 ansible_ssh_host=192.0.2.2

[etcd]
k8s-etcd01 ansible_ssh_host=192.0.2.3
k8s-etcd02 ansible_ssh_host=192.0.2.4
k8s-etcd03 ansible_ssh_host=192.0.2.5

[kube-node]
k8s-node01 ansible_ssh_host=192.0.2.6
k8s-node02 ansible_ssh_host=192.0.2.7
k8s-node03 ansible_ssh_host=192.0.2.8

[k8s-cluster:children]
kube-node
kube-master

Ansible を流して Kubernetes をインストールしよう

インスタンスと inventory ができたら、 Kubespray の Ansible playbook を流して Kubernetes をインストール & クラスタ構築をします。

ansible-playbook を実行する際に、コマンドラインオプションで Kubernetes のバージョンや OS の種類、 network plugin を指定することが可能です。 今回は cloud_provider=openstack kube_network_plugin=weave bootstrap_os=centos を指定してみました。

$ ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -i kubespray/inventory/inventory -b kubespray/cluster.yml --extra-vars "cloud_provider=openstack bootstrap_os=centos kube_network_plugin=weave" --forks 50 --timeout 600 -K
SUDO password:

  ~ 中略 ~
  
PLAY RECAP ****************************************************************************************************************************
k8s-etcd01           : ok=154  changed=44   unreachable=0    failed=0
k8s-etcd02           : ok=151  changed=44   unreachable=0    failed=0
k8s-etcd03           : ok=151  changed=44   unreachable=0    failed=0
k8s-master01         : ok=325  changed=69   unreachable=0    failed=0
k8s-master02         : ok=321  changed=70   unreachable=0    failed=0
k8s-node01           : ok=319  changed=71   unreachable=0    failed=0
k8s-node02           : ok=292  changed=61   unreachable=0    failed=0
k8s-node03           : ok=291  changed=61   unreachable=0    failed=0
localhost            : ok=3    changed=0    unreachable=0    failed=0

初期構築にはおよそ40分かかりました。長い…

k8s-master01 にログインして kubectl コマンドを叩いてみます。 kubectl get nodes を実行すると、用意した VM が一覧で表示され、 cluster として認識されている様子が確認できました。

VERSION に coreos の記述が見えますが、これは Kubespray が取得している hyperkube の Repository Tag の表記が見えているようです。

$ kubectl get nodes -o wide
NAME           STATUS                     AGE       VERSION           EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION
k8s-master01   Ready,SchedulingDisabled   2h        v1.6.7+coreos.0   <none>        CentOS Linux 7 (Core)   3.10.0-327.28.2.el7.x86_64
k8s-master02   Ready,SchedulingDisabled   2h        v1.6.7+coreos.0   <none>        CentOS Linux 7 (Core)   3.10.0-327.28.2.el7.x86_64
k8s-node01     Ready                      2h        v1.6.7+coreos.0   <none>        CentOS Linux 7 (Core)   3.10.0-327.28.2.el7.x86_64
k8s-node02     Ready                      2h        v1.6.7+coreos.0   <none>        CentOS Linux 7 (Core)   3.10.0-327.28.2.el7.x86_64
k8s-node03     Ready                      2h        v1.6.7+coreos.0   <none>        CentOS Linux 7 (Core)   3.10.0-327.28.2.el7.x86_64

k8s 関連のコンポーネントも kube-system namepsace の pod として動作しているようです。

$ kubectl get pods --all-namespaces
NAMESPACE     NAME                                   READY     STATUS    RESTARTS   AGE
kube-system   kube-apiserver-k8s-master01            1/1       Running   0          2h
kube-system   kube-apiserver-k8s-master02            1/1       Running   0          2h
kube-system   kube-controller-manager-k8s-master01   1/1       Running   0          2h
kube-system   kube-controller-manager-k8s-master02   1/1       Running   1          2h
kube-system   kube-dns-3841192733-qwtwg              3/3       Running   0          2h
kube-system   kube-dns-3841192733-t515c              3/3       Running   0          2h
kube-system   kube-proxy-k8s-master01                1/1       Running   0          2h
kube-system   kube-proxy-k8s-master02                1/1       Running   0          2h
kube-system   kube-proxy-k8s-node01                  1/1       Running   0          2h
kube-system   kube-proxy-k8s-node02                  1/1       Running   0          2h
kube-system   kube-proxy-k8s-node03                  1/1       Running   0          2h
kube-system   kube-scheduler-k8s-master01            1/1       Running   0          2h
kube-system   kube-scheduler-k8s-master02            1/1       Running   0          2h
kube-system   kubedns-autoscaler-1833630871-437zl    1/1       Running   0          2h
kube-system   nginx-proxy-k8s-node01                 1/1       Running   0          2h
kube-system   nginx-proxy-k8s-node02                 1/1       Running   0          2h
kube-system   nginx-proxy-k8s-node03                 1/1       Running   0          2h
kube-system   weave-net-1k6dl                        2/2       Running   0          2h
kube-system   weave-net-3r2mw                        2/2       Running   0          2h
kube-system   weave-net-d8fkb                        2/2       Running   0          2h
kube-system   weave-net-dk0kz                        2/2       Running   0          2h
kube-system   weave-net-dx9qs                        2/2       Running   0          2h

master の apiserver へのリクエストは、各 node に存在する nginx-proxy を介して行われます。 nginx-proxy の backend は各 master の apiserver となっており、 apiserver へ接続するためのローカルロードバランサとして動作します。

kubespray/docs/ha-mode.md at master · kubernetes-sigs/kubespray

Deploy a Production Ready Kubernetes Cluster. Contribute to kubernetes-sigs/kubespray development by creating an account on GitHub.

アプリケーションをデプロイし動作させてみる

試しに、 nginx の pod を立ち上げ、 welcome ページが見られるか確認してみます。

## nginx の pod を立ち上げてみる
$ kubectl run nginx --image=nginx:1.13 --port=80
deployment "nginx" created
$ kubectl get pods
NAME                     READY     STATUS              RESTARTS   AGE
nginx-1122307028-hpxxw   0/1       ContainerCreating   0          7s


## service を作って NodePort で叩けるようにする
$ kubectl expose deploy nginx --name=nginx-nodeport --port=80 --target-port=80 --type="NodePort"
service "nginx-nodeport" exposed
$ kubectl get services
NAME             CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes       10.233.0.1      <none>        443/TCP        2h
nginx-nodeport   10.233.20.109   <nodes>       80:30968/TCP   6s


## curl で叩いてみる
$ curl http://localhost:30968/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>


## pod の数を増やしてみる
$ kubectl scale --replicas=6 deployment/nginx
deployment "nginx" scaled
$ kubectl get pods -o wide
NAME                     READY     STATUS    RESTARTS   AGE       IP             NODE
nginx-1122307028-2f2f4   1/1       Running   0          12s       10.233.88.2   k8s-node02
nginx-1122307028-9thvz   1/1       Running   0          12s       10.233.64.4   k8s-node01
nginx-1122307028-hpxxw   1/1       Running   0          1m        10.233.64.3   k8s-node01
nginx-1122307028-hthb1   1/1       Running   0          12s       10.233.96.3   k8s-node03
nginx-1122307028-pls2s   1/1       Running   0          12s       10.233.88.3   k8s-node02
nginx-1122307028-szc8p   1/1       Running   0          12s       10.233.96.2   k8s-node03

Kubernetes Cluster の scale 方法

ノード追加

k8s の node を増やしたくなったらどうすればよいか。 scale 用の playbook が用意されており、 VM を増やしたあとにそれを適用することで実現できるようでした。

まずは Terraform のコードで VM の count を増やす。んで terraform apply

count = "3"  ->  "6"

inventory に追加した node を追記し、 Ansible を流す。

$ ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -i kubespray/inventory/inventory -b kubespray/scale.yml --extra-vars "cloud_provider=openstack bootstrap_os=centos kube_network_plugin=weave" --forks 50 --timeout 600 -K

  ~ 中略 ~

PLAY RECAP ****************************************************************************************************************************
k8s-etcd01           : ok=1    changed=0    unreachable=0    failed=0
k8s-etcd02           : ok=1    changed=0    unreachable=0    failed=0
k8s-etcd03           : ok=1    changed=0    unreachable=0    failed=0
k8s-master01         : ok=1    changed=0    unreachable=0    failed=0
k8s-master02         : ok=1    changed=0    unreachable=0    failed=0
k8s-node01           : ok=265  changed=6    unreachable=0    failed=0
k8s-node02           : ok=244  changed=3    unreachable=0    failed=0
k8s-node03           : ok=244  changed=3    unreachable=0    failed=0
k8s-node04           : ok=278  changed=66   unreachable=0    failed=0
k8s-node05           : ok=278  changed=66   unreachable=0    failed=0
k8s-node06           : ok=279  changed=66   unreachable=0    failed=0

Ansible 実行にはおよそ 40 分かかった。

kubectl で node の一覧を確認してみると、追加した node0{4..6} 3台が一覧に居ることを確認できた。

$ kubectl get nodes -o wide
NAME           STATUS                     AGE       VERSION           EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION
k8s-master01   Ready,SchedulingDisabled   3h        v1.6.7+coreos.0   <none>        CentOS Linux 7 (Core)   3.10.0-327.28.2.el7.x86_64
k8s-master02   Ready,SchedulingDisabled   3h        v1.6.7+coreos.0   <none>        CentOS Linux 7 (Core)   3.10.0-327.28.2.el7.x86_64
k8s-node01     Ready                      3h        v1.6.7+coreos.0   <none>        CentOS Linux 7 (Core)   3.10.0-327.28.2.el7.x86_64
k8s-node02     Ready                      3h        v1.6.7+coreos.0   <none>        CentOS Linux 7 (Core)   3.10.0-327.28.2.el7.x86_64
k8s-node03     Ready                      3h        v1.6.7+coreos.0   <none>        CentOS Linux 7 (Core)   3.10.0-327.28.2.el7.x86_64
k8s-node04     Ready                      11m       v1.6.7+coreos.0   <none>        CentOS Linux 7 (Core)   3.10.0-327.28.2.el7.x86_64
k8s-node05     Ready                      11m       v1.6.7+coreos.0   <none>        CentOS Linux 7 (Core)   3.10.0-327.28.2.el7.x86_64
k8s-node06     Ready                      11m       v1.6.7+coreos.0   <none>        CentOS Linux 7 (Core)   3.10.0-327.28.2.el7.x86_64

ノード削除

逆に、node の台数を減らしたくなったらどうすればよいか?

まずは、 drain で新規 pod の配備の禁止と実行中の pod の停止を行い、 delete で対象の node をクラスタから外す。

$ kubectl cordon k8s-node04
$ kubectl drain k8s-node04 --force
$ kubectl delete node k8s-node04

その後、 Terraform の count の値を減らして terraform apply することで、 クラスタを構成する node を減らすことができます。

count = "6"  ->  "3"

Kubernetes の Upgrade

kubespray/docs/upgrades.md at master · kubernetes-sigs/kubespray

Deploy a Production Ready Kubernetes Cluster. Contribute to kubernetes-sigs/kubespray development by creating an account on GitHub.

k8s のバージョンを新しくしたくなった場合は、初期構築と同様に Ansible を流すことでアップデートができます。upgrade-cluster.yml を適用することで、ノードの cordon, drain, uncording を行う Graceful なアップグレードが行われます。 (バージョンを指定して cluster.yml を流し直すこともできるらしいが Unsafe な方法)

アップデートは、最新の kubespray リポジトリを取得後、 kube_version 変数でアップデート先の k8s のバージョンを指定し Ansible を流します。 kube_version のほか、docker_versionflannel_version のように、各コンポーネントごとにバージョンを指定してアップデートすることもできるようです。

## 最新の kubespray を取得
$ cd kubespray
$ git fetch origin
$ git checkout origin/master

## プロジェクトディレクトリの root に戻り upgrade-cluster.yml を適用
$ cd ../
$ ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -i kubespray/inventory/inventory -b kubespray/upgrade-cluster.yml --extra-vars "cloud_provider=openstack kube_network_plugin=weave bootstrap_os=centos kube_version=v1.7.2" --forks 50 --timeout 600 -K

  ~ 中略 ~

PLAY RECAP ************************************************************************************************************************************************************************
k8s-etcd01           : ok=133  changed=4    unreachable=0    failed=0
k8s-etcd02           : ok=129  changed=7    unreachable=0    failed=0
k8s-etcd03           : ok=129  changed=7    unreachable=0    failed=0
k8s-master01         : ok=296  changed=17   unreachable=0    failed=0
k8s-master02         : ok=288  changed=16   unreachable=0    failed=0
k8s-node01           : ok=271  changed=13   unreachable=0    failed=0
k8s-node02           : ok=262  changed=11   unreachable=0    failed=0
k8s-node03           : ok=262  changed=11   unreachable=0    failed=0
localhost            : ok=3    changed=0    unreachable=0    failed=0

Ansible が完走するのにかかった時間はおよそ1時間。

アップグレード完了後 kubectl version 見ると、 Server Version が上がっていることが確認できます。

$ kubectl version
2017-08-18 17:05:09.424573 I | proto: duplicate proto type registered: google.protobuf.Any
2017-08-18 17:05:09.424715 I | proto: duplicate proto type registered: google.protobuf.Duration
2017-08-18 17:05:09.424748 I | proto: duplicate proto type registered: google.protobuf.Timestamp
Client Version: version.Info{Major:"1", Minor:"7", GitVersion:"v1.7.2+coreos.0", GitCommit:"c6574824e296e68a20d36f00e71fa01a81132b66", GitTreeState:"clean", BuildDate:"2017-07-24T23:28:22Z", GoVersion:"go1.8.3", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"7", GitVersion:"v1.7.2+coreos.0", GitCommit:"c6574824e296e68a20d36f00e71fa01a81132b66", GitTreeState:"clean", BuildDate:"2017-07-24T23:28:22Z", GoVersion:"go1.8.3", Compiler:"gc", Platform:"linux/amd64"}

所感

  • Ansible が流れ終わるのにめちゃ時間がかかる
    • Large Cluster の場合は Ansible 高速化の TIPS 類を試したほうが良さそう
  • 今回の検証では Service type: Load Balancer は試せていない
    • 社内の OpenStack 環境を利用したが、諸々の事情で Neutron LBaaS を導入していないため選択不可
  • inventory の自動生成は Ansible の Dynamic Inventory を活用するときれいになりそう
  • Kubespray が持っている Ansible コードと自分達で記述した Ansible コードを共存させる仕組みが必要そう
    • DOCKER_OPTS の変更や、サービス要件に合わせて一部設定ファイルに手を加えたいケースなど
    • リポジトリを切り替えながらアレコレ適用するのは面倒、一発で環境構築したいマン
    • ドキュメントのこの内容を見てみようと思う - Kubespray (kargo) in own ansible playbooks repo

余談

検証中、 Weave のインストールで Ansible がコケたので、 PR を送ってみました。

Verify if br_netfilter module exists by yukirii · Pull Request #1492 · kubernetes-sigs/kubespray

Fixed #1272 In the installation process of Weave network plugin, br_netfilter module is loaded unconditionally. (#1152) When br_netfilter module is built-in to the kernel and not a loadable kernel ...

Kubespray のリポジトリは CNCF の Contributor License Agreement に署名する必要があるようで、 Bot が何やら小難しいことを書き込んできたのでビビりました… 😅


comments powered by Disqus