エキサイト株式会社のみーです。
最近はコンテナばかり触っていますが、要件によってはEC2の非コンテナ環境で構築しなければいけないこともあると思います。そういう時に出てくるのが、構成管理どうしよう、という悩み。
ヘルパースクリプトを活用する
AWSに触れ始めた頃、CFnで全てを完結させたいという謎のモチベーションがありました。CFnのヘルパースクリプトを使うことで、その目的は達せられます。
例えば、SESにリレーするようにPostfixを設定したEC2を構築するとなると、以下のような感じ。
Resources: EC2Instance: Type: AWS::EC2::Instance Metadata: AWS::CloudFormation::Init: config: files: /etc/cfn/cfn-hup.conf: content: !Sub | [main] stack=${AWS::StackId} region=${AWS::Region} interval=3 mode: "000400" owner: root group: root /etc/cfn/hooks.d/cfn-auto-reloader.conf: content: !Sub | [cfn-auto-reloader-hook] triggers=post.update path=Resources.EC2Instance.Metadata.AWS::CloudFormation::Init action=/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource EC2Instance --region ${AWS::Region} runas=root mode: "000400" owner: root group: root /tmp/sasl_passwd: content: | [email-smtp.us-east-1.amazonaws.com]:587 foo:bar mode: "000600" owner: root group: root commands: 01_postconf: command: | postconf -e \ "relayhost = [email-smtp.us-east-1.amazonaws.com]:587" \ "smtp_sasl_auth_enable = yes" \ "smtp_sasl_security_options = noanonymous" \ "smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd" \ "smtp_use_tls = yes" \ "smtp_tls_security_level = encrypt" \ "smtp_tls_note_starttls_offer = yes" \ "smtp_tls_CAfile = /etc/ssl/certs/ca-bundle.crt" 02_cp_sasl_passwd: command: cp /tmp/sasl_passwd /etc/postfix/sasl_passwd 03_postmap: command: postmap hash:/etc/postfix/sasl_passwd 04_chown_db: command: chown root:root /etc/postfix/sasl_passwd.db 05_chmod_db: command: chmod 600 /etc/postfix/sasl_passwd.db services: sysvinit: cfn-hup: enabled: true ensureRunning: true files: - /etc/cfn/cfn-hup.conf - /etc/cfn/hooks.d/cfn-auto-reloader.conf postfix: enabled: true ensureRunning: true files: - /tmp/sasl_passwd Properties: ImageId: !Ref ImageId KeyName: !Ref KeyName SubnetId: !Ref SubnetId AvailabilityZone: !Ref SubnetAz InstanceType: !Ref InstanceType SecurityGroupIds: - !Ref SecurityGroupId UserData: Fn::Base64: !Sub | #!/bin/bash -xe yum update -y aws-cfn-bootstrap /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource EC2Instance --region ${AWS::Region} /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource EC2Instance --region ${AWS::Region}
設定ファイルやコマンドなどをテンプレート内に記述するわけですが・・・見ての通り、決して美しいとは言えません。もちろん最適化の余地はあるのですが、このまま使い続ける気にはなりません。本来の目的は構成管理であって、テンプレートを美しく記述することではないわけです。
ということで、別の方法を考えることに。
Ansibleを使う
AWSの構成管理サービスといえばAWS OpsWorksですね。PuppetやChefを使い慣れているのであればベターな選択かと思います。
なのですが、エキサイトでは構成管理ツールにAnsibleを採用することが多かったりします。個人的にもAnsibleは使いやすくて好きなので、今回はOpsWorksは使いません。
AWSでAnsibleプレイブックを実行する場合、SSMドキュメントの AWS-ApplyAnsiblePlaybooks
を利用するのがおすすめです。
事前にS3にプレイブックをアップロードしておき、あとはマネコンなどからポチるだけで実行されます。とても快適。
以下のサンプルでは、ansible-playbook-${AWS::Region}-${AWS::AccountId}
というS3バケットを用意しておき、playbook.zip
という名前で対象のプレイブックをアップロードしていることが前提になっています。
Resources: EC2Instance: Type: AWS::EC2::Instance Properties: ImageId: !Ref ImageId KeyName: !Ref KeyName SubnetId: !Ref SubnetId AvailabilityZone: !Ref SubnetAz InstanceType: !Ref InstanceType SecurityGroupIds: - !Ref SecurityGroupId ApplyAnsiblePlaybooks: Type: AWS::SSM::Association Properties: Name: AWS-ApplyAnsiblePlaybooks AssociationName: sample-association WaitForSuccessTimeoutSeconds: 300 Targets: - Key: InstanceIds Values: - !Ref EC2Instance OutputLocation: S3Location: OutputS3BucketName: !Sub ansible-playbook-${AWS::Region}-${AWS::AccountId} OutputS3KeyPrefix: log Parameters: SourceType: - "S3" SourceInfo: - !Sub | {"path": "https://ansible-playbook-${AWS::Region}-${AWS::AccountId}.s3-${AWS::Region}.amazonaws.com/playbook.zip"} InstallDependencies: - "True" PlaybookFile: - "playbook.yml" ExtraVariables: - "SSM=True" Check: - "False" Verbose: - "-v"
これをデプロイすれば、良い感じにEC2インスタンスが作成されて、良い感じにAnsibleプレイブックが実行されます。プレイブックを変更した場合は、SSMのステートマネージャーから即座に適用できます。いいね。
トラブルシューティング
上記のテンプレート、EC2を閉域網に構築した場合には失敗します。InstallDependencies
パラメータをTrueにすると、Ansibleや依存関係にあるPythonなどのミドルウェアをインストールしてくれるのですが、Amazon Linux 2の場合は以下のようなコマンドが実行されます。
sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm sudo yum install -y ansible
思いっきり外に出ようとしているな。
が、そういうことであれば InstallDependencies
パラメータはFalseにしておき、その前に AWS-RunShellScript
でExtras LibraryからAnsibleをインストールしてしまえば良さそう。
InstallAnsible: Type: AWS::SSM::Association Properties: Name: AWS-RunShellScript AssociationName: sample-association WaitForSuccessTimeoutSeconds: 300 Targets: - Key: InstanceIds Values: - !Ref EC2Instance OutputLocation: S3Location: OutputS3BucketName: !Sub ansible-playbook-${AWS::Region}-${AWS::AccountId} OutputS3KeyPrefix: log Parameters: commands: - sudo amazon-linux-extras install ansible2 -y
S3のVPCエンドポイントを設定しておく必要はありますが、これなら閉域網でも問題ありませんね。
おわりに
CFnでは、AWSリソースの構成管理だけに徹するのが良いのかなと思います。銀の弾丸は存在しませんので、最適なツールを選択していきたいところです。