RancherにTerraform + AWSでホスト(RancherOS)を自動追加

はじめに

Rancher環境にAWSのスポットインスタンスでホストを追加したいときってありますよね。でもスポットインスタンスはいつ消されるかわからない環境なので単体でRancherのホストとして使っていても、いつかきっと意図しないタイミング消されます。そんなことが無いように今回はスポットインスタンスをAuto Scaling Groupに関連付けて、かつRancher環境に自動でホストとして登録してみましょう。(しかもTerraformで!)

構成

構成としては以下のようになります。TerraformでAuto Scaling Groupを作成しつつ、User-Dataを使ってRancher Server(今回はtry.rancher.com)に自動登録します。

 

コードについて

今回使用するコードの完成形はsupersoftware/tf-aws-try-rancher-hostsに置いてます。また、このterraform内で使用しているmoduleもこちらに置いてます。これらのコードはほぼほぼgreensheep/terraform-aws-rancher-hostsから作っています。

変更している点としては主に

  • terraformで自前で作ったrancher-serverがあることを前提にしないようにした
  • ↑の関係でホスト削除の際にSQSでrancher-serverにホスト自動削除依頼がされない
  • ↑の関係でSQSが必要なくなるのでIAMリソースの作成もしない
  • 立ち上げるAMIはRancherOSにしている

ことです。

AWSにTerraformで作るリソース

セキュリティグループ( aws_security_group )

Rancherに追加するホストたちが使用するセキュリティグループを作ります。 IPSec のために UDP の 500 と 4500 を開けておきます。

オートスケールのLaunch Configuration( aws_launch_configuration )

オートスケールに使用するLaunch Configurationを作ります。主にEC2の情報になります。ポイントとしては先に作っておいたセキュリティグループのIDを使うようにすることと、スポットインスタンスであれば spot_price で希望する価格を記載することです。AMIの最新に関してはこちらで確認ができるので適宜見ておいたほうが良いです。

Auto Scaling Group( aws_autoscaling_group

オートスケールに使うAuto Scaling Groupです。ここでどれだけのインスタンス数にするか決めます。

User Dataを使ったホスト(EC2)のRancherへの自動登録

Terraformの aws_launch_configuration には user_data を渡すことができます。EC2の初期化時にこれが発火するのですが、RancherOSであればここに cloud-config を渡すことができます。公式ドキュメントの「Running RancherOS on AWS」にも3番で述べられています(ファイルでも良いしテキスト渡してもいい)。

さて、自動登録するためにやりたいことは以下のとおりです。

  1. rancher-serverの対象Environmentから registrationToken を取得
  2. EC2のメタ情報を取得(IPアドレスとか)
  3. 登録コマンド発火して rancher-agent を起動

これをやるためには curl がほしくなるんですけど、残念ながらRancherOSには curl がありません。そこで curl だけ入ったコンテナ(supersoftware/curl)を使ってコマンド発火させることにしました。注意点として、これにはコンテナを使うため、 docker が使えないといけません。 cloud-config には runcmd という場所でスクリプトを実行できるのですが、ここで書くコマンドは docker が使えるようになる前に実行されますので、この中で docker を使うと必ず失敗します。そういうときの為に /etc/rc.local にスクリプトを書いておく手法があります。これは docker の起動状態を気にせずに発火するスクリプトなので、ここで wait-for-docker という特別なコマンドを使うことで、必ず docker が使える状態を保証することができます。 cloud-config を以下のように記述することで /etc/rc.local を作成することができます。

#cloud-config
write_files:
- path: /etc/rc.local
permissions: "0755"
owner: root
content: |
#!/bin/bash
wait-for-docker
# Setup initial vars
serverUrl=https://${environment_access_key}:${environment_secret_key}@${server_hostname}
projectId=${environment_id}
curlCmd="docker run --rm supersoftware/curl"
# Make initial POST request for a registration token and record the id
response=$($curlCmd -s -X POST $serverUrl/v1/registrationtokens?projectId=$projectId)
requestId=$(echo $response | jq -r '.id')
requestState=$(echo $response | jq -r '.state')
# The registration token request is async so keep checking until it's complete
while [[ "$requestState" != "active" ]]; do
sleep 2
response=$($curlCmd -s $serverUrl/v1/registrationtokens/$requestId)
requestState=$(echo $response | jq -r '.state')
done
# Get the instance id from metadata
instanceId=$($curlCmd -s http://169.254.169.254/latest/meta-data/instance-id)
instancePrivateIp=$($curlCmd -s http://169.254.169.254/latest/meta-data/local-ipv4)
# Labels
instanceLabels="HOSTID=$instanceId&CLOUD=aws&CLUSTER=${cluster_name}"
customLabels="${cluster_instance_labels}"
if [ -n "$customLabels" ]; then
instanceLabels="$instanceLabels&$customLabels"
fi
# Add external DNS label if there's a public IP address
instancePublicIp=$($curlCmd -f -s http://169.254.169.254/latest/meta-data/public-ipv4)
if [ -n "$instancePublicIp" ]; then
instanceLabels="$instanceLabels&io.rancher.host.external_dns_ip=$instancePublicIp"
fi
# Use the command in the response to start the rancher agent
cmd=$(echo $response | jq -r '.command')
# add environment variables when launching the agent
eval $${cmd/sudo docker run /sudo docker run -e CATTLE_AGENT_IP=$instancePrivateIp -e CATTLE_HOST_LABELS=\"$instanceLabels\" }

ここで注意する点としては、YAMLに慣れてる人はすぐ気づくでしょうけど、公式ドキュメント通りに記述すると

#cloud-config
rancher:
write_files:
- path: /etc/rc.local
permissions: "0755"
owner: root
content: |
#!/bin/bash
wait-for-docker

このように、 write_files の下の - path: はインデントしています。これは不正なので、インデントは無くしてください(これのせいでかなり悩まされました。。。)。もう一つ、上のデータはTerraformのtemplate( userdata.template )として用意しています。なので、terraformが埋める変数がそのままの記述(e.g. ${environment_key})になっています。これは terraform apply する際に rendered され、自分で設定した値に置換されるようになっています。

デプロイ!

supersoftware/tf-aws-try-rancher-hostsから terraform のコード clone し、必要な情報を埋めて terraform plan → terraform apply してみましょう。 try.rancher.com の指定したEnvironmentにホストが自動で登録されるはずです。

おわりに

Rancherにホストをオートスケールで自動登録する方法について紹介しました。実際はスポットインスタンスが停止するときにRancher Serverに連絡してホストを削除するようにしないと、お亡くなりになったホストがRancher管理下に残り続けます。そこらへんはオートスケールのライフサイクルフックをうまく駆使して本番ではお掃除するようにしましょう。

APN Consulting Partner
スーパーソフトウエアはAWSパートナーネットワーク(APN)のコンサルティングパートナーです。