CloudFormation、便利ですよね。インフラのコード化はインフラの状態の変化を可視化できるというたいへん素晴らしいメリットがあります。くわえてCloudFormationはスタック単位で作成・削除ができるため、WEBコンソールから手動で作成・削除するときに起こりうる「不要なリソースが残ったまま」や「必要なリソースを削除してしまう」というリスクが減ることで心理的・金銭的なコストが軽減される点がわたしは気に入っています。
さて当エントリでは「CloudFormationを使ってEC2インスタンスを立ち上げる」という基本的な作業において気になったUbuntuの起動システムについて調査した経過を記します。
前提
本番環境で稼働しているUbuntuベースのインスタンス(以後「稼働インスタンス」と呼びます)が存在します。この稼働インスタンスと同等のインスタンス(以後「テストインスタンス」と呼びます)を作成し、あるテストをおこないたいと思います。 そこで、稼働インスタンスのAMIをWEBコンソールから作成し、このAMIをもとにテストインスタンスをCloudFormationから作成することにしました。この際、稼働インスタンスでは各種デーモンが動いているのですがテストインスタンスにおいてはこれらを動かしたくありません。
どうする?
CloudFormationには各種ヘルパースクリプトが用意されています。
ヘルパースクリプトは Amazon Linux AMI の最新バージョンにプレインストールされています。他の UNIX/Linux AMI で使用するために、Amazon Linux yum リポジトリから入手することもできます。
これらはAmazon Linux AMIですとプレインストールされていますがUbuntuベースのAMIですと意図的にインストールしてやる必要があります。
このヘルパースクリプトのうちcfn-init
というスクリプトを使えばテストインスタンス起動時のデーモンの起動を無効にすることができそうです。
cfn-init ヘルパースクリプトは、AWS::CloudFormation::Init キーからテンプレートメタデータを読み取り、それに応じて次のような操作を行います。
- CloudFormation のメタデータの取得と解析
- パッケージのインストール
- ディスクへのファイルの書き込み
- サービスの有効化/無効化と開始/停止
http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-init.html
具体的にはAWS::CloudFormation::Init
キーにおいてservices
キーを定義することでサービスの起動設定を行えるようです。
このとき、
Linux システムでは、このキーは sysvinit を使用してサポートされています。
とありますが、ここで sysvinit
とはなにを意味するのか?よくわかりませんので調べてみました。
sysvinit とは?
sysvinitはSystemV initの略で,UNIX SystemV(システムファイブ)と呼ばれるAT&T社謹製の古典的なUNIXが採用した起動メカニズムと同じ動作をするように設計されたソフトウェアです。
とありました。さらに調べるとこの仕組みはUbuntuにおいてはUbuntu 6.10 (Edgy Eft)においてUpstart
に、 Ubuntu 15.04 (Vivid Vervet)においてsystemd
に取って代わられたとのことです。
ということは、Ubuntuにおける直近のLTSリリースである14.04
も16.04
もsysvinit
をサポートしていないのでしょうか?これではcfn-init
は使えません。
とりあえずトライしてみることにしました。
cronデーモンを例にトライ
以下のようなテンプレートを用意しました。少々長いですが引用します。
1 ---
2 Description: sysvinit test
3 Parameters:
4 KeyName:
5 Type: String
6 VpcId:
7 Type: String
8 SubnetId:
9 Type: String
10 Image:
11 Description: AMI
12 Type: String
13 AllowedValues:
14 - ami-8c4055eb # Ubuntu Server 14.04
15 - ami-785c491f # Ubuntu Server 16.04
16 Default: ami-785c491f # Ubuntu Server 16.04
17 Resources:
18 WebAccessGroup:
19 Type: AWS::EC2::SecurityGroup
20 Properties:
21 GroupDescription: Enable outgoing HTTP(S) access
22 SecurityGroupEgress:
23 - IpProtocol: tcp
24 FromPort: '80'
25 ToPort: '80'
26 CidrIp: 0.0.0.0/0
27 - IpProtocol: tcp
28 FromPort: '443'
29 ToPort: '443'
30 CidrIp: 0.0.0.0/0
31 VpcId: !Ref VpcId
32 SSHGroup:
33 Type: AWS::EC2::SecurityGroup
34 Properties:
35 GroupDescription: Enable global SSH access
36 SecurityGroupIngress:
37 - IpProtocol: tcp
38 CidrIp: 0.0.0.0/0
39 FromPort: 22
40 ToPort: 22
41 SecurityGroupEgress:
42 - IpProtocol: tcp
43 CidrIp: 0.0.0.0/0
44 FromPort: 22
45 ToPort: 22
46 VpcId: !Ref VpcId
47 TestServer:
48 Type: AWS::EC2::Instance
49 Properties:
50 ImageId: !Ref Image
51 InstanceType: t2.micro
52 KeyName: !Ref KeyName
53 AvailabilityZone: ap-northeast-1a
54 SubnetId: !Ref SubnetId
55 SecurityGroupIds:
56 - !Ref SSHGroup
57 - !Ref WebAccessGroup
58 UserData:
59 Fn::Base64:
60 Fn::Sub:
61 - |
62 #!/bin/bash -xe
63 apt-get update
64 apt-get -y install python-pip
65 pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz
66 cp -a /usr/local/init/ubuntu/cfn-hup /etc/init.d/cfn-hup
67
68 chmod u+x /etc/init.d/cfn-hup
69 /usr/local/bin/cfn-init -v \
70 --stack ${StackName} \
71 --region ${Region} \
72 --resource TestServer
73 - StackName: {Ref: "AWS::StackName"}
74 Region: {Ref: "AWS::Region"}
75 Metadata:
76 AWS::CloudFormation::Init:
77 config:
78 services:
79 sysvinit:
80 cron:
81 enabled: 'false'
82 ensureRunning: 'false'
このテンプレートにおいて今回の肝は58~74行目のUserData
キーと75行目以降のMetadata
キーになります。
UserData
にてcfn-init
ヘルパースクリプトをインストールし、停止したいサービスを列挙したMetadata
キー配下をcfn-init
使用時に指定することで、テストインスタンス起動時にcronデーモンを停止することを目的としています。
たとえばcfn-init
を作動させた状態でUbuntu 14.04
テストインスタンスを立ち上げたいときはaws-cli
を用いて以下のように実行することで試すことができます。Image
パラメータにUbuntu 14.04
のAMIを指定していることに注意してください。
$ aws cloudformation create-stack \
--stack-name cfn-init-test-ubuntu-14-cron-off \
--template-body file:///path/to/cfn-init-test.template \
--parameter ParameterKey=Image,ParameterValue=ami-8c4055eb \
ParameterKey=KeyName,ParameterValue=key-name \
ParameterKey=VpcId,ParameterValue=vpc-id \
ParameterKey=SubnetId,ParameterValue=subnet-id
同様にcfn-init
を作動させない状態でUbuntu 16.04
テストインスタンスを立ち上げたいときは58行目以降を削除したのちに、
- 58 UserData:
- 59 Fn::Base64:
- 60 Fn::Sub:
- 61 - |
- 62 #!/bin/bash -xe
- 63 apt-get update
- 64 apt-get -y install python-pip
- 65 pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz
- 66 cp -a /usr/local/init/ubuntu/cfn-hup /etc/init.d/cfn-hup
- 67
- 68 chmod u+x /etc/init.d/cfn-hup
- 69 /usr/local/bin/cfn-init -v \
- 70 --stack ${StackName} \
- 71 --region ${Region} \
- 72 --resource TestServer
- 73 - StackName: {Ref: "AWS::StackName"}
- 74 Region: {Ref: "AWS::Region"}
- 75 Metadata:
- 76 AWS::CloudFormation::Init:
- 77 config:
- 78 services:
- 79 sysvinit:
- 80 cron:
- 81 enabled: 'false'
- 82 ensureRunning: 'false'
先述のようにaws-cli
を用いて以下のように実行します。Image
パラメータにUbuntu 16.04
のAMIを指定していることに注意してください。
$ aws cloudformation create-stack \
--stack-name cfn-init-test-ubuntu-16-cron-off \
--template-body file:///path/to/cfn-init-test.template \
--parameter ParameterKey=Image,ParameterValue=ami-785c491f \
ParameterKey=KeyName,ParameterValue=key-name \
ParameterKey=VpcId,ParameterValue=vpc-id \
ParameterKey=SubnetId,ParameterValue=subnet-id
ちなみにスタックが作成されるとWEBコンソールから以下のような表示が見られると思います。
このように、各バージョンのUbuntuにおいてcfn-init
の有効・無効を切り替えて試してみました。
結果、cfn-init
を作動させた場合はcronデーモンは停止状態で、cfn-init
を作動させない場合はcronデーモンは稼働状態でした。
これはUbuntu 14.04
とUbuntu 16.04
の双方で同様の現象でした。これらはsysvinit
を採用していないはずなのになぜでしょう?
まずは各々の実際に稼働した起動システムを調べてみます。
Ubuntu 14.04 の起動システム
ubuntu14$ sudo stat /proc/1/exe
File: '/proc/1/exe' -> '/sbin/init'
Size: 0 Blocks: 0 IO Block: 1024 symbolic link
Device: 3h/3d Inode: 9323 Links: 1
Access: (0777/lrwxrwxrwx) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2017-06-28 07:43:10.904461000 +0000
Modify: 2017-06-28 06:03:36.184461000 +0000
Change: 2017-06-28 06:03:36.184461000 +0000
Birth: -
ubuntu14$ /sbin/init --version
init (upstart 1.12.1) # <= upstart
Copyright (C) 2006-2014 Canonical Ltd., 2011 Scott James Remnant
This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
ubuntu@ip-172-31-25-228:~$
Ubuntu 16.04 の起動システム
ubuntu16$ sudo stat /proc/1/exe
File: '/proc/1/exe' -> '/lib/systemd/systemd' # <= systemd
Size: 0 Blocks: 0 IO Block: 1024 symbolic link
Device: 4h/4d Inode: 9340 Links: 1
Access: (0777/lrwxrwxrwx) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2017-06-28 06:13:46.476000000 +0000
Modify: 2017-06-28 06:13:46.472000000 +0000
Change: 2017-06-28 06:13:46.472000000 +0000
Birth: -
調べたところ予想どおりUbuntu 14.04
ではUpstart
が、Ubuntu 16.04
ではsystemd
が採用されていることがわかります。
どうしてsysvinit
でないのにcfn-init
は所望の動作をしてくれたのでしょうか?
cfn-init
のソースを追っていくとサービスの有効・無効化にはupdate-rc.d
を使用している ことがわかりました。
ログからもその動作が垣間見えます。
# /var/log/cfn-init.log@ubuntu14
2017-06-28 06:14:28,533 [DEBUG] Using service modifier: /usr/sbin/update-rc.d
2017-06-28 06:14:28,533 [DEBUG] Setting service cron to disabled
2017-06-28 06:14:28,639 [INFO] disabled service cron
2017-06-28 06:14:28,639 [DEBUG] Using service runner: /usr/sbin/service
2017-06-28 06:14:28,654 [DEBUG] Stopping service cron as it is running
2017-06-28 06:14:28,778 [INFO] Stopped cron successfully
2017-06-28 06:14:28,779 [INFO] ConfigSets completed
# /var/log/cfn-init.log@ubuntu16
2017-06-28 06:04:00,068 [DEBUG] Using service modifier: /usr/sbin/update-rc.d
2017-06-28 06:04:00,069 [DEBUG] Setting service cron to disabled
2017-06-28 06:04:00,080 [INFO] disabled service cron
2017-06-28 06:04:00,081 [DEBUG] Using service runner: /usr/sbin/service
2017-06-28 06:04:00,087 [DEBUG] Stopping service cron as it is running
2017-06-28 06:04:00,098 [INFO] Stopped cron successfully
2017-06-28 06:04:00,099 [INFO] ConfigSets completed
update-rc.d
は下記のとおり、System V スタイルの起動設定をサポートしているとのこと。その意味ではcfn-init
の説明と合致します。
update-rc.d updates the System V style init script links /etc/rcrunlevel.d/NNname whose target is the script /etc/init.d/name. These links are run by init when it changes runlevels; they are generally used to start and stop system services such as daemons. runlevel is one of the runlevels supported by init, namely, 0123456789S, and NN is the two-digit sequence number that determines where in the sequence init will run the scripts.
http://manpages.ubuntu.com/manpages/precise/man8/update-rc.d.8.html
残る疑問
さきほど調べたとおり今回のテストサーバの起動システムはUpstart
およびsystemd
でした。sysvinit
とは合致しません。どうしてcronデーモンの停止は実現したのでしょうか?またcronデーモン以外のサービスでも同様の動きになるでしょうか?
まとめ
今回はここまでです。次回は先述の疑問を解明すべくさらにいろいろ試したいと思います。