前回の記事の続き。前回はオンプレ(自分が管理するVM)でWebSocketサーバをスケールアウトする方法を書いたが、本稿はクラウド(AWS)のマネージドサービスを使った方法について書く。
記事の内容
- WebSocketを使ったWebアプリケーションをAWS上に構築する。
- AMI作成にPackerを利用し、AWS構築にはTerraformを使う。
前提
- AWSのルートアカウント持っている。
- インターネットで名前解決できるドメインを持っていて、Route53で管理している。
システムアーキテクチャ
ロードバランサーにはApplication Load Balancer (ALB) を利用する。
ALBの後段にはアプリケーションサーバのEC2を二つ配置する。ALBでSSL終端するため、ALB-EC2間はHTTP/WebSocketで通信する。
EC2間の情報連携用のPub/SubにはAmazon ElastiCache for Redisを利用する。
認証局により証明されたSSL証明書を発行するためRoute53を使ってDNS認証する。
Packer用IAMユーザ作成
IAMグループ作成
- グループ名に「packer」と記入し、「次のステップ」をクリック
- 「AmazonEC2FullAccess」ポリシーを選択し、「次のステップ」をクリック
- 「グループの作成」をクリック
IAMユーザ作成
- IAMユーザーページにアクセス
- 「ユーザーを追加」をクリック
- ユーザー名に「packer」と記入し、アクセスの種類「プログラムによるアクセス」を選択し、「次のステップ:アクセス権限」をクリック
- グループ「packer」を選択し、「次のステップ:タグ」をクリック
- キー「Name」、値「packer」を記入し、「次のステップ:確認」をクリック
- 「ユーザーの作成」をクリック
- 「アクセスキーID」と「シークレットアクセスキー」をメモしておく。
PackerでEC2のマシンイメージ(AMI)作成
Packerインストール
以下は執筆時点のコマンド。最新のインストール手順についてはこちらを参照。
# curl -O https://releases.hashicorp.com/packer/1.4.2/packer_1.4.2_linux_amd64.zip
# unzip packer_1.4.2_linux_amd64.zip
# mv packer /usr/local/sbin/
# packer version
Packer v1.4.2
認証情報
認証情報を環境変数に書き込む。
IAMユーザ作成時にメモしたpackerユーザのアクセスキーIDとシークレットアクセスキーを環境変数に書き込む。
export AWS_ACCESS_KEY_ID=xxxx
export AWS_SECRET_ACCESS_KEY=xxxx
AMI構成情報を作成
以下のjsonファイルを作成する。
DockerとDocker Composeのインストール、ソースコードのダウンロード、Dockerイメージの作成をAMIの中で済ませておく。
{
"variables": {
"aws_access_key": "{{env `AWS_ACCESS_KEY_ID`}}",
"aws_secret_key": "{{env `AWS_SECRET_ACCESS_KEY`}}",
"region": "ap-northeast-1"
},
"builders": [
{
"access_key": "{{user `aws_access_key`}}",
"ami_name": "packer-linux-aws-demo-{{timestamp}}",
"instance_type": "t2.micro",
"region": "{{user `region`}}",
"secret_key": "{{user `aws_secret_key`}}",
"source_ami_filter": {
"filters": {
"name": "amzn2-ami-hvm-*-x86_64-gp2"
},
"owners": ["137112412989"],
"most_recent": true
},
"ssh_username": "ec2-user",
"type": "amazon-ebs"
}
],
"provisioners": [{
"type": "shell",
"inline": [
"sleep 30",
"sudo yum -y install docker git",
"sudo systemctl start docker",
"sudo systemctl enable docker",
"sudo curl -L \"https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m)\" -o /usr/local/bin/docker-compose",
"sudo chmod +x /usr/local/bin/docker-compose",
"git clone https://github.com/tetsis/simple-chat.git",
"sudo cp -r simple-chat /root/",
"sudo /usr/local/bin/docker-compose -f /root/simple-chat/docker-compose.yml build"
]
}]
}
packer validate
コマンドでjsonファイルの構文チェックをする。
# packer validate app-server.json
Template validated successfully.
Template validated successfully.
が表示されればOK。
packer build
コマンドでAMIを作成する。
最終行の ami-...
をメモしておく。(ここでは xxxx
としているが、実際は英数字の文字列)
# packer build app-server.json
...
==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs: AMIs were created:
ap-northeast-1: ami-xxxx
EC2インスタンス用ロールを作成
EC2インスタンス内でawsコマンドを実行するため、ロールを作成する。
- IAMロールページにアクセス
- 「ロールを作成」をクリック
- 「このロールを使用するサービスを選択」で「EC2」を選択し、「次のステップ:アクセス権限」をクリック
- 「AmazonEC2ReadOnlyAccess」ポリシーを選択し、「次のステップ:タグ」をクリック
- キー「Name」、値「ec2-role」を記入し、「次のステップ:確認」をクリック
- ロール名「ec2-role」を記入し、「ロールの作成」をクリック
TerraformでAWSリソース作成
Terraform用IAMグループとIAMユーザ作成
Packer用にIAMグループとIAMユーザを作ったのと同様にTerraform用にも作成する。
作り方はPackerの時と同じなので詳細については省略する。
Packerとの違いはIAMグループにアタッチするポリシーで、今回の例では以下の5つのポリシーをアタッチする。
- AmazonEC2FullAccess
- AmazonElastiCacheFullAccess
- AmazonRoute53FullAccess
- AWSCertificateManagerFullAccess
- IAMFullAccess
Terraformインストール
以下は執筆時点のコマンド。最新のインストール手順についてはこちらを参照。
# curl -O https://releases.hashicorp.com/terraform/0.12.5/terraform_0.12.3_linux_amd64.zip
# unzip terraform_0.12.3_linux_amd64.zip
# mv terraform /usr/local/sbin/
# terraform version
Terraform v0.12.3
AWSリソース作成
Terraformの.tfファイルはこちらを参照。
公開したくない情報は環境変数で設定してから terraform plan
で正常性を確認して、 terraform apply
でリソース作成を実行する。
(環境変数は ~/.bashrc
等に書いておいても良い)
# export TF_VAR_access_key=xxxx
# export TF_VAR_secret_key=xxxx
# export TF_VAR_allow_ip=x.x.x.x/32
# export TF_VAR_ami=ami-xxxx (Packerで作成したAMI)
# export TF_VAR_app_fqdn=simple-chat.xx.xx
# export TF_VAR_zone_id=xxxx (自分がホストしているドメインのZONE ID)
# cd /dir/to/terraform
# ls
aws_acm.tf aws_alb.tf aws_ec2.tf aws_elasticache.tf aws_r53.tf aws_sg.tf aws.tf aws_tg.tf aws_variables.tf aws_vpc.tf terraform.tfvars
# terraform plan
...
Plan: 25 to add, 0 to change, 0 to destroy.
...
# terrafrom apply
...
Enter a value: yes
...
(リソース作成。しばらく待つ)
Apply complete! Resources: 25 added, 0 changed, 0 destroyed. (「Apply complete!」が表示されたらリソース作成は正常に完了している。)
(参考)EC2初期処理用スクリプト
TerraformのEC2インスタンス作成ファイルで以下のような記述があるが、このuser_dataの作成方法について書いておく。
user_data = <<EOF
IyE...
EOF
user_dataはEC2インスタンス起動時の処理をシェルスクリプト形式で書いたものをbase64エンコードしたものである。
シェルスクリプトの中身はEC2インスタンスを起動してからでないと決まらないパラメータをタグから取得してアプリケーションの設定ファイルに書き込む、という操作を行っている。
参考のためbase64変換前のシェルスクリプトを記載する。
# !/bin/sh
aws="/usr/bin/aws --region ap-northeast-1"
logger="logger -t $0"
get_instance_id()
{
instance_id=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
}
get_redis_endpoint()
{
redis_endpoint=$(${aws} ec2 describe-instances \
--instance-id ${instance_id} \
--query 'Reservations[].Instances[].Tags[?Key==`Redis_endpoint`].Value' \
--output text)
}
set_redis_endpoint()
{
sed -i -e "s/REDIS_HOST.*$/REDIS_HOST: '${redis_endpoint}'/g" docker-compose.yml
}
${logger} "start $0"
sleep 60
${logger} "get info"
get_instance_id
get_redis_endpoint
${logger} "set environment"
cd /root/simple-chat
set_redis_endpoint
${logger} "run app"
docker-compose up -d
${logger} "finished $0"
exit 0
確認
Webブラウザで https://(環境変数TF_VAR_app_fqdnに設定したFQDN)
にアクセスする。
simple-chatの画面が表示され、テキストボックスに入力した結果がテキストエリアに表示されたら正常に動作している。
後始末
検証が終わったら忘れずにリソースを削除する。
# terraform destroy
...
Enter a value: yes
...