OpenShiftとAWSのAuto Scaling Groupで伸縮自在なKubernetesクラスタを構築する

レッドハットの林です。

本記事は赤帽エンジニアAdvent Calendar 2018の11日目です。(時間オーバー)


OpenShiftはv3.11からAWSのAuto Scaling Groupに対応しています。つまり、下記のようなことが実現できます。

  • Auto Scaling Groupのインスタンス数を増減させるだけでワーカーノードを追加・削除できる
  • Pod数が増え、リソースが逼迫すると自動的にワーカーノードが追加される

商用版のOpenShiftはCore数に応じた年単位の料金モデルなのでメリットを完全には活かしきれないですが、それでも簡単にワーカーノードを増減できるというのは機能として嬉しいポイントでしょう。

利用方法は以下にドキュメント化されていますが、この手順に従っても成功しない(何でや)ので、解説記事を書いておきます。

https://docs.openshift.com/container-platform/3.11/admin_guide/cluster-autoscaler.html

OpenShiftにおける新規ノード追加

OpenShiftでは、v3.9まではノード追加のためにansibleコマンドを実行する必要がありました。

v3.10からは、前準備さえできていればワーカーノードを起動するだけでクラスタにノードを追加できます。

このあたりを深掘りするため、OpenShiftがワーカーノードをクラスタに追加するためのクライアント認証について振り返ってみます。

OpenShiftで新規ノードを認証する流れ

Kubernetesでは、ワーカーノードがKubernetesのAPIと通信する際、クライアント証明書で認証することを推奨しています。

OpenShiftでももちろん、クライアント証明書を使ってワーカーノードの通信に認証をかけていますが、OpenShift v3.9まではこの証明書をansibleで生成してノードに配布していました。

  • 新規ノードの追加
  • クラスタ内部の証明書更新

の際、ansibleのplaybookを実行する必要があったわけですね。

しかしv3.10からは、ワーカーノードの証明書作成、更新はKubernetesのTLS bootstrappingという方法で自動化されています。

https://docs.openshift.com/container-platform/3.11/architecture/infrastructure_components/kubernetes_infrastructure.html#node-bootstrapping

TLS Bootstrappingの簡単な流れは以下です。

  • ワーカーノードでkubeletが起動する
  • ワーカーノードは、bootstrap用のkubeconfigファイルを探す
  • bootstrap用のkubeconfigを読み込み、KubernetesのAPIに制限された権限で接続する
  • kubeletは証明書用のCSRを生成し、APIに送る
  • APIサーバーにあるcontroller managerによりCSRが承認されれば、証明書がワーカーノードに発行される

最後のCSRの承認には二通りの方法があり、

  • CSRを自動的に承認するカスタムコントローラー(Pod)を導入する
  • 手動(kubectlコマンド)でクラスタ管理者が承認する

OpenShiftでは、上記の方法をそれぞれ以下のように対応しています。

CSRを自動的に承認するカスタムコントローラー(Pod)を導入する

openshift-ansible実行時に、openshift_master_bootstrap_auto_approve変数をtrueにする(デフォルトfalse)

https://docs.openshift.com/container-platform/3.11/install/configuring_inventory_file.html

これにより、CSRを承認してくれるPod、bootstrap-autoapproverがクラスタにデプロイされます。

手動で承認する

oc get csrコマンドで未承認のCSRを確認して、oc adm certificate approve csr-xxxで承認する

つまり、OpenShift v3.10からは、ノードを追加するのにansibleコマンドを実行する必要は無く、準備ができたワーカーノードを立ち上げるだけでよい、ということですね。

OpenShiftで、ワーカーノードを簡単に追加できる仕組みを作る

ノードを立ち上げるだけでOpenShiftクラスタに参加させるには、かなりの前準備が必要です。

  • OpenShiftがインストールされたAMI(Primed AMIと呼ぶらしい)を用意する
  • 上記AMIからVMを起動する際、cloud-initでkubeconfigを配置したり適切なサービスをsystemctlで起動したりして、VM起動時にノードがクラスタに参加できるようにする
  • OpenShiftクラスタにbootstrap-autoapproverをインストールして、起動したワーカーノードを自動的に承認できるようにする

さらに、次回の記事に譲りますが以下も実施することでより弾力性のあるクラスタを実現できます。

  • cluster-autoscalerを導入して、リソースが逼迫したときにAuto Scaling Groupから自動的に必要なだけのワーカーノードが起動されるようにする

OpenShiftがあらかじめインストールされたAMI(Primed AMI)を作成する

まず、OpenShiftがインストールされたVMイメージを用意する必要があります。

OpenShiftでは、そのためにPlaybookが用意されているので、それを実行するだけです。

...といいたいところですが、このPlaybookを実行するにはAWSのAPIにアクセスするためのライブラリが必要です。

RHELでは、以下でインストールできます。

 - curl -kL https://bootstrap.pypa.io/get-pip.py | python
 - pip install boto3 --upgrade
 - pip install boto --upgrade

AMI作成Playbook、build_ami.ymlは以下のように実行します。

export LANG=C
ansible-playbook -i </path/to/inventory/file> \
    /usr/openshift-ansible/playbooks/aws/openshift-cluster/build_ami.yml \
    -e @build-ami-provisioning-vars.yaml

ここで重要なことは、export LANG=Cでシェルの環境をデフォルトにしておくことです。

上記Playbookの中でOpenShiftをインストールするためにsubscription-managerを使ってサブスクリプションを登録しに行っているのですが、登録済みか否かを判定するために標準出力のメッセージを利用しており、これが日本語メッセージだと上手く判定できないようです。
そのため、OpenShiftをAMIにインストールする際に失敗します。

ここで利用するインベントリはシンプルに以下のようにします。

[OSEv3:children]
masters
nodes
etcd

[OSEv3:vars]
openshift_deployment_type=openshift-enterprise
ansible_ssh_user=ec2-user
openshift_clusterid=xxxxx # 参加させたいクラスタのクラスタ名
ansible_become=yes

[masters]
[etcd]
[nodes]

また、変数ファイルは以下のようにします。既存のクラスタが稼働している環境を設定してください。

openshift_deployment_type: openshift-enterprise

# 参加させたいクラスタのクラスタ名
openshift_aws_clusterid: xxx

# 既存のクラスタを実行中のAWSリージョン名
openshift_aws_region: ap-northeast-1

# VPCを新しく作成するか
openshift_aws_create_vpc: false 

# クラスタ実行中のVPC名
openshift_aws_vpc_name: xx

# クラスタ実行中のサブネット(一つで良い)
openshift_aws_subnet_az: ap-northeast-1a

openshift_aws_create_security_groups: false 

# ansible-playbook実行中のマシンからsshログインできるkeypair
openshift_aws_ssh_key_name: xxx

# RHEL Gold Image (RHEL-7.5_HVM_GA-20180322-x86_64-1-Access2-GP2)
openshift_aws_base_ami: ami-2a0f5d4c

# s3バケットの作成有無
openshift_aws_create_s3: false

# AMIビルドに作成するEC2インスタンスに設定するセキュリティグループ
# インターネット経由でPublic IPに向けてSSH接続に行くので、アクセスを受け付けられるようなセキュリティグループを設定する
openshift_aws_build_ami_group: xxx

# 既存のクラスタが実行されているVPCとサブネットの情報
openshift_aws_vpc: 
  name: "{{ openshift_aws_vpc_name }}"
  cidr: 10.0.0.0/16
  subnets:
    ap-northeast-1:
    - cidr: 10.0.0.0/19
      az: "ap-northeast-1a"
    - cidr: 10.0.32.0/19
      az: "ap-northeast-1c"
    - cidr: 10.0.64.0/19
      az: "ap-northeast-1d"

container_runtime_docker_storage_type: overlay2 
container_runtime_docker_storage_setup_device: /dev/xvdb

openshift_aws_ami_build_set_gquota_on_slashfs: true 

# Red Hatアカウントの認証情報
rhsub_user: xxxx
rhsub_pass: xxxx

# OpenShiftのサブスクリプション Pool ID
rhsub_pool: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

上記のファイルを作成してPlaybookを実行すると、ワーカーノードのAMIが作成されます。

ワーカーノード起動時にクラスタに参加できるようcloud-initを設定する

上記手順で作成したAMIを使うと、OpenShiftがインストールされた状態でVMを起動できます。

しかし、実際に既存のクラスタにそのVMをワーカーノードとして参加させるためには、OpenShift masterへの接続情報を設定し、OpenShiftのワーカーサービスをsystemctlで起動しなければなりません。

さらに、OpenShift v3.11から、OpenShiftの実行に必要はシステムコンテナは、認証が掛かっている registry.redhat.io からダウンロードされます。

そのため、その認証情報もVM起動時に設定する必要があります。

以下は、VM起動時に設定するuserdataを作成する例です。これを設定してVMを起動することで、既存のクラスタにワーカーノードを参加させることができます。

# ワーカーノード起動時にAPI接続するためのkubeconfigのダウンロード
ansible -i <OpenShiftインストール時に/使った/inventoryへの/パス> masters -m fetch -a "src=/etc/origin/master/bootstrap.kubeconfig dest=./bootstrap.kubeconfig flat=yes"

# ワーカーノードでシステムコンテナをredhat.ioからダウンロードするためのdocker credentialのダウンロード
ansible -i <OpenShiftインストール時に/使った/inventoryへの/パス> masters -m fetch -a "src=/var/lib/origin/.docker/config.json dest=./dockerconfig.json flat=yes"

cat <<EOF > user-data.txt
#cloud-config
write_files:
- path: /root/openshift_bootstrap/openshift_settings.yaml
  owner: 'root:root'
  permissions: '0640'
  content: |
    openshift_node_config_name: node-config-compute
- path: /etc/origin/node/bootstrap.kubeconfig
  owner: 'root:root'
  permissions: '0640'
  encoding: b64
  content: |
    `base64 ./bootstrap.kubeconfig | sed '2,$s/^/    /'`
- path: /var/lib/origin/.docker/config.json
  owner: 'root:root'
  permissions: '0644'
  encoding: b64
  content: |
    `base64 ./dockerconfig.json | sed '2,$s/^/    /'`
runcmd:
- [ mkdir, -p, /etc/origin/cloudprovider ]
- [ touch, /etc/origin/cloudprovider/aws.conf ]
- [ ansible-playbook, /root/openshift_bootstrap/bootstrap.yml]
- [ systemctl, restart, systemd-hostnamed]
- [ systemctl, restart, NetworkManager]
- [ systemctl, enable, atomic-openshift-node]
- [ systemctl, start, atomic-openshift-node]
EOF

VMを起動して、OpenShiftにワーカーノードとして登録されることを確認する

上記で作成したuser-data.txtの内容を指定して、EC2インスタンスを起動してみましょう。

このとき、既存のワーカーノードと同じサブネット、セキュリティグループを選択して、masterとネットワークが繋がるように気をつけます。

これで、待っていればクラスタにノードが追加されるはずです。

watch oc get nodes

されませんね。。。

ワーカーノードの承認

ワーカーノードは、前述の通り、クラスタに追加するためにCSRを承認する必要があります。

以下コマンドで、未承認のCSRの一覧を見ることができます。

$ oc get csr

NAME                 AGE       REQUESTOR                                                 CONDITION
node-csr-xxx-xxxxx   2m        system:serviceaccount:openshift-infra:node-bootstrapper   Pending

ここでは、とりあえず手動でこのCSRを承認します。

$ oc adm certificate approve node-csr-xxx-xxxxx

certificatesigningrequest.certificates.k8s.io/node-csr-xxx-xxxxx approved

あらためて、ノードを確認すると。。

$ oc get nodes
NAME                                             STATUS    ROLES          AGE       VERSION
ip-x-x-xx-xx.ap-northeast-1.compute.internal   Ready     compute        1m        v1.11.0+d4cacc0

ノードが追加されました!

ワーカーノード承認の自動化

最終的には、クラスタの利用状況に応じて動的にノードを追加したり削除したりしたいわけです。

なので、手動でいちいちノードを承認するわけにはいきません。

そこで、OpenShiftではこのノードのCSRを自動的に承認してくれるカスタムコントローラー(Pod)を導入できます。

OpenShiftインストール時に使用したinventoryに以下変数を追加してします。

[OSEv3:vars]
# ...
openshift_master_bootstrap_auto_approve=true

このinventoryを使ってOpenShiftのインストール用Playbookを再実行すれば、自動承認用のPodがクラスタにインストールされます。

$ cd /usr/share/ansible/openshift-ansible
$ ansible-playbook -i <inventoryファイルへの/パス> playbooks/deploy_cluster.yml 

実行が終わったら、Podがインストールされているか確認します。

$ oc get pods -n openshift-infra

NAME                       READY     STATUS    RESTARTS   AGE
bootstrap-autoapprover-0   1/1       Running   0          10s

これにより、oc adm certificate approveコマンドをいちいち実行しなくても自動的にノードのCSRが承認される状態となりました。

Auto Scaling Groupでワーカーノードを追加する

さて、先ほどと同様に、EC2インスタンスをダイレクトに使ってもワーカーノードを追加できるのですが、せっかくなのでAuto Scaling Groupを使ってみます。

さらに、spotインスタンスを使ってコストを抑えてみましょう。

起動テンプレートの作成

Auto Scaling Groupを起動するためのテンプレートを以下より作成します。

https://ap-northeast-1.console.aws.amazon.com/ec2/v2/home?region=ap-northeast-1#LaunchTemplates:sort=launchTemplateId

<Launch Templates> => <起動テンプレートの作成>を選択し、必要事項を埋めていきます。

以下を参考にしてください。

  • AMI IDをPrimed AMIのIDにする
  • セキュリティグループは既存のワーカーノードと同じものを設定する
  • ネットワークインターフェース、ストレージは指定不要
  • Purchasing optionでチェックを入れる(Spotインスタンス利用)
  • 「ユーザーデータ」に上記で生成したものを入れる

起動テンプレートからAuto Scaling Groupを作成する

起動テンプレートを作成したら、そのテンプレートからAuto Scaling Groupを作成しましょう。

  • Auto Scaling GroupのタグでKubernetesのクラスタタグkubernetes.io/cluster/<クラスタ名>ownedにする
  • スケーリングポリシーを「このグループを初期のサイズに維持する」に設定する

インスタンス数を増減させ、OpenShiftのノードに反映されることを確認する

では、Auto Scaling Groupのインスタンスを増減させて、ノードが増えるか確認してみます。

watch oc get nodes

Every 2.0s: oc get nodes                                                                                                   

NAME                                             STATUS    ROLES          AGE       VERSION
ip-xx-xx-xx-xx.ap-northeast-1.compute.internal   Ready     compute        11d       v1.11.0+d4cacc0
ip-xx-xx-xx-xx.ap-northeast-1.compute.internal   Ready     infra,master   11d       v1.11.0+d4cacc0
ip-xx-xx-xx-xx.ap-northeast-1.compute.internal   Ready     compute        11d       v1.11.0+d4cacc0
ip-xx-xx-xx-xx.ap-northeast-1.compute.internal    Ready     compute        1m        v1.11.0+d4cacc0

増えましたね!
これで、Auto Scaling Groupのインスタンス数を増減させるだけでクラスタのサイズを変更できるようになりました。

まとめ

次回は、cluster-autoscalerを導入し、クラスタのリソースが逼迫したときに自動的にクラスタのサイズを増やす、というのをやってみます。

cluster-autoscalerは、Podのデプロイがリソース不足によりPendingになっているときに、指定のAuto Scaling Groupのインスタンス数を適切な数まで増やしてくれるものです。

このような機構を使いこなせば、クラスタの管理もどんどん楽になっていきそうですね。

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません