Ansible という自動化ツールを使って AWS EC2 t3.nano インスタンス上にボタンひとつで Redmine を作ります。一旦作るとパラメータを変えて何度もデプロイしたり、複数の Redmine を簡単にデプロイできるというのが味噌です。t3.nano というのはラズパイ程度のパワーがあるそうです。当初 AWS CloudFormation だけでやろうとしたのですが、謎のおまじないが多く嫌になったので Ansible を併用する事にしました。
ここで出来た Redmine はデプロイし直すとデータが消えるので実用性はありませんが、話が長くなるので最低限の部分だけ書きます。
前準備
以下の準備ができている事前提です。
- AWS CLI:
aws configure
が終わっている。 - Ansible: 私は
brew install ansible
でインストールしました。 - Docker: 出来るだけ使わないで済まそうかと思いましたが楽さに負けました。確認のため手元にインストールしておきます。
SSH key pair の作成
これから作る EC2 にアクセスする鍵の作成です。ここだけ手動です。
aws ec2 create-key-pair --key-name hogekey --query 'KeyMaterial' --output text > hogekey.pem
chmod 400 hogekey.pem
EC2 の準備
まず CloudFormation の設定ファイルを cfn_redmine.yml という名前で作ります。
AWSTemplateFormatVersion: "2010-09-09"
Resources:
WebServer:
Type: AWS::EC2::Instance
Properties:
InstanceType: t3.nano # ラズパイ程度のインスタンス
ImageId: ami-02892a4ea9bfa2192 # Amazon Linux 2 64-bit x86
KeyName: hogekey # 先程作った鍵の名前
SecurityGroups:
- Ref: WebServerSecurityGroup
WebServerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Redmine security
SecurityGroupIngress:
- CidrIp: 0.0.0.0/0 # 全世界から見える設定
FromPort: '3000' # Redmine のために開けるポート
IpProtocol: tcp
ToPort: '3000'
- CidrIp: xxx.xxx.xxx/32 # ご自宅の IP。自宅からしか見えないように。
FromPort: '22' # SSH のために開けるポート
IpProtocol: tcp
ToPort: '22'
Outputs:
PublicIP:
Value: !GetAtt WebServer.PublicIp
WebsiteURL:
Value: !Sub "http://${WebServer.PublicDnsName}:3000"
まずはこれだけで動くか確認します。
# まず実行
$ aws cloudformation create-stack --stack-name redmine --template-body file://cfn_redmine.yml
# しばらく待って出来上がった IP を確認
$ aws cloudformation describe-stacks --stack-name redmine | grep OutputValue
"OutputValue": "52.196.137.57",
"OutputValue": "http://ec2-52-196-137-57.ap-northeast-1.compute.amazonaws.com:3000",
# 返ってきた IP の ec2-user ユーザにログインする。
$ ssh -i hogekey.pem ec2-user@52.196.137.57
Redmine を手元で動かす
色々試しましたが結局 redmine を一番簡単に動かすには docker が楽だと分かったので、次のような docker-compose.yml ファイルを作成します。
version: '3.1'
services:
redmine:
image: redmine
restart: always
ports:
- 3000:3000
environment:
REDMINE_DB_MYSQL: db
REDMINE_DB_PASSWORD: example
REDMINE_SECRET_KEY_BASE: supersecretkey
REDMINE_DB_ENCODING: utf8mb4
db:
image: mysql:5.7
restart: always
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
environment:
MYSQL_ROOT_PASSWORD: example
MYSQL_DATABASE: redmine
念の為手元でちゃんと動くか確認します。docker stack というやつの方が堅牢らしいですが、ログを見るのが面倒なので docker-compose を使いました。
起動
docker-compose up
http://localhost:3000/ にアクセスして Redmine が動いているか確認。admin/admin でログイン出来ます。
Ctrl + C で起動した docker-compose を止めてから削除
docker-compose down
Redmine をリモートで動かす
確認した docker-compose.yml を Ansible で EC2 上で動かします。Ansible の操作対象を指定するには、hosts という名前のファイル((inventory と呼びます) を作り、先程 describe-stacks で取得した IP と EC2 のユーザ名を以下のように書き込みます。これで Ansible から aws
というグループ名で操作出来ます。
[aws]
52.196.137.57 ansible_user=ec2-user
EC2 の初期設定を済ませ docker-compose を実行する Ansible の設定ファイル (playbook と呼びます) です。configure.yml という名前で作ります。ここの味噌は t3.nano のメモリが無さすぎるのでスワップメモリを用意する部分です。
- name: root で実行する設定
hosts: aws # 操作対象の EC2 インスタンスのグループ名
become: yes # root で実行する。
tasks:
- name: yum の更新
command: yum update -y
- name: Docker インストール
yum:
name: docker
- name: ec2-user を docker グループに追加
user:
name: ec2-user
append: yes
groups: docker
- name: グループ追加が反映されるように一旦接続を切る
meta: reset_connection
- name: Systemd による Docker 自動起動
systemd:
name: docker
enabled: yes
state: started
- name: docker-compose インストール
shell: |
curl -L https://github.com/docker/compose/releases/download/1.29.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
- name: Swap メモリが設定済か調べる
stat: path=/swapfile
register: swapfile
- name: Swap が未設定なら t3.nano ではメモリが足りないので 512M の Swap を作る
when: swapfile.stat.exists == False
shell: |
dd if=/dev/zero of=/swapfile bs=128M count=4
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
swapon -s
echo "/swapfile swap swap defaults 0 0" >> /etc/fstab
- name: ec2-user で実行する設定
hosts: aws # 操作対象の EC2 インスタンスのグループ名
tasks:
- name: docker-compose.yml をアップロード
copy:
src: docker-compose.yml
dest: /home/ec2-user
owner: ec2-user
group: ec2-user
mode: "0644"
- name: docker-compose 起動
command: docker-compose up -d
ここまで実行してみます。
ansible-playbook -i hosts configure.yml --private-key hogekey.pem
全部一発で動かす。
いよいよ本題です。ここまでは AWS の設定に CloudFormation、サービスのインストールと起動に Ansible Playbook という適材適所で自動化を進めました。これをコマンド一つで繋げられないでしょうか? これを実現するには、Ansible はCloudFormation で作成した EC2 の IP アドレスを知る必要があります。
上記の方法では Ansible の接続先を hosts ファイルに設定しました。このままでは EC2 を作る度に手動で hosts ファイルを書き換えないといけません。そこで、CloudFormation を Ansible 経由で起動して IP アドレスを取得する事にします。
今から provision.yml という名前でファイルを作ります。まず、この playbook はローカルで動くので hosts: localhost
と指定します。
- name: AWS を構築する
hosts: localhost
gather_facts: false
connection: local
Ansible には cloudformation を実行するモジュールが用意されているので、cloudformation を呼ぶ事自体は簡単です。
tasks:
- name: CloudFormation 作成
cloudformation:
stack_name: redmine
disable_rollback: true
template: cfn_redmine.yml
出来上がった EC2 の情報を収集するには ec2_instance_info
を使います。集めた情報は ec2_list
という変数に入ります。ここで、redmine スタックで作った EC2 だけをフィルタするために "tag:aws:cloudformation:stack-name": redmine
を使っています。これは CloudFormation が自動でつけてくれる tag です。DB など二種類以上の EC2 を作る場合は自分で tag をつけて見分ける必要があります。
- name: EC2 情報取得
ec2_instance_info:
filters:
instance-state-name: [ "running" ]
"tag:aws:cloudformation:stack-name": redmine
register: ec2_list
作った EC2 を捜査対象 (inventory) に加えるには add_host
を使います。ここで先程集めた ec2_list
はリストなので loop 構文で要素を取り出します。集めたホスト名をユーザ名(ec2-user 固定) とともに aws グループに追加します。これは上記 hosts ファイルで言うと [aws]
以下に記入するのと同じです。
- name: EC2 を inventory に入れる
add_host:
name: "{{ item.public_dns_name }}"
ansible_user: ec2-user
host_key_checking: false
groups: aws
when: ec2_list.instances|length > 0
loop: "{{ ec2_list['instances'] | flatten(levels=1) }}"
早速作った aws グループにアクセスします。EC2 が起動して SSH 接続が有効になるまでしばらく待ちます。
- name: EC2 の完成待ち
hosts: aws
gather_facts: false
vars:
ansible_ssh_common_args: "-o StrictHostKeyChecking=no"
tasks:
- name: wait for instances to become available
wait_for_connection:
最後に先程作った configure.yml を実行します。
- import_playbook: configure.yml
実行方法。実行対象は localhost と動的に集めた EC2 なので -i で inventory を指定する必要はありません。
ansible-playbook provision.yml --private-key hogekey.pem
一旦 aws cloudformation delete-stack --stack-name redmine
で削除してから上を実行すると、本当にボタン一発で redmine が出来上がるのを確認出来ます。
参考
-
https://muhannad0.github.io/post/cloudformation-and-ansible-provision-and-configure/
- ほぼここに書いてある事をパクリました。
- Ansible Working With Playbooks: https://docs.ansible.com/ansible/2.9/user_guide/playbooks.html
- この旧版のドキュメントの方がわかりやすいと感じました。日本語もありますが表示が崩れているので併用すると良いです。
-
https://hub.docker.com/_/redmine
- Redmine の Docker オフィシャル
-
https://knooto.info/docker-redmine4-setup/
- 寿司ビール問題の参考にしました。
-
https://stackoverflow.com/questions/26677064/create-and-use-group-without-restart
- ec2-user を docker group に追加しても即反映されず焦りました。