Chef12に対応したAWS OpsWorksにRailsアプリをデプロイする①

昨日の社内年度末ローカルミーティングでニックネームが「マニラ」になった前田です。

弊社ではAWSでアプリケーションをデプロイする際、インフラ構築の自動化&省力化の為のDevOpsツールとして主にAWSのOpsWorksを利用しています。
昨年12月にAmazonからアナウンスがあった通り、AWS OpsWorksでChef12を利用することが出来るようになりました。
将来的にChef11サポートが終了することを見越して、Chef12に対応したOpsWorksで動くRailsアプリケーションCookbookの骨組みを作成する、というミッションを与えられましたので今回取り組みました。

Chef12になり大きく変わる点は、まずはbuilt-inで用意されていたLayerが無くなる、ということで、自分達でCookbookを一から用意する必要があるということです。
また、スタック情報やデータバッグの取得方法が変わった為、opsworks-cookbooksをそのまま使用することなどは出来ません。
全く一から作り直す、ということになります。

まずはCookbookをどのようにOpsWorksに配置するか、ということから考えました。

OpsWorksへCookbook配備設計

Chef12バージョンのOpsWorksでは、Chef11バージョンの時にあったManage Berkshelfの項目が無くなりました。

Chef11バージョン Chef12バージョン

今まではBerkshelfレシピ使う時は、Berksfileに使いたいレシピを記載し、アップロードするだけでOpsworks上で使用できたのですが、これからはローカルでレシピをインストールしてアップロードしなければなりません。
Berkshelfとカスタムで使用するレシピを上手く統合して使いたいので、AWSドキュメントのベストプラクティスを参考に下記のような手順でクックブックをアップロードする設計にしました。

ポイントは、ローカルでBerkshelfレシピをインストールせず、CirclCIでBerksehlfレシピインストール・カスタムCookbookとの統合、などを実行することと、Custom jsonをレポジトリ内で管理して、合わせてアップデートする、というところです。
サービス毎にCookbookレポジトリを作成する、という方針で設計しているので、Stackに設定するCustom Jsonは、Cookbooksレポジトリの中に入れてしまおう、ということです。

Stack作成

あらかじめVPCやSSH_key、S3レポジトリ、S3レポジトリのGET権限を持つIAMを作成しておきます。
OpsWorksの画面で真ん中の Chef 12 Stack を選択し、作成していきます。
Use custom Chef cookbooksで、Repository typeS3 Archiveにします。

Cookbookレポジトリのディレクトリ構成

Cookbookのディレクトリ構成は下記のようにしました。

├── Berksfile
├── berks_cookbooks
│   └── Berksfile
├── custom_cookbooks
│   └── 自分で作成するカスタムレシピ郡
├── config
│  └── custom_json.yml
├── circle.yml
└── circleci
    ├── .aws
    │ └── credentials
    └── scripts
      └── install_berks_cookbooks.sh
      └── update_stack.rb
Berksfile

berks package cookbooks.tar.gzコマンドで、各Cookbookを圧縮するための読込先パスを書きます。

source 'https://supermarket.chef.io'
# Berkshelf Cookbooks
cookbook 'yum-epel',          path: "./berks_cookbooks/cookbooks/yum-epel"
cookbook 'ruby_build',        path: "./berks_cookbooks/cookbooks/ruby_build"
cookbook 'mysql',             path: "./berks_cookbooks/cookbooks/mysql"
cookbook 'nginx',             path: "./berks_cookbooks/cookbooks/nginx"
# Custome Cookbooks
cookbook 'time-zone',         path: "./custom_cookbooks/time-zone"
cookbook 'rbenv',             path: "./custom_cookbooks/rbenv"
cookbook 'ruby',              path: "./custom_cookbooks/ruby"
cookbook 'setup',             path: "./custom_cookbooks/setup"
cookbook 'deploy',            path: "./custom_cookbooks/deploy"
berks_cookbooks/Berksfile

berks installコマンドで、Berksehlf Cookbookをダウンロードする為のBerksfileです。

source 'https://supermarket.chef.io'
cookbook 'yum-epel'
cookbook 'ruby_build'
cookbook 'rbenv'
cookbook 'ruby'
cookbook 'nginx'
cookbook 'mysql', '~> 5.3.6'
circle.yml

CircleCI上で、BerkshelfコミュニティCookbookインストールCustom CookbookとコミュニティCookbookを圧縮S3にアップロードインスタンスにCookbookアップデートStackのCustom Jsonアップデートを実行します。

machine:
  ruby:
    version: 2.0.0
general:
  branches:
    ignore:
      - master
deployment:
  master
    branch: master
    commands:
      - gem install berkshelf
      - bash ./circleci/scripts/install_berks_cookbooks.sh
      - sudo pip install awscli
      - mv ./circleci/.aws ~/
      - echo '[opsworks_iam]' >> ~/.aws/credentials
      - echo 'aws_access_key_id = '$AWS_OPS_WORKS_ACCESS_KEY_ID >> ~/.aws/credentials
      - echo 'aws_secret_access_key = '$AWS_OPS_WORKS_SECRET_ACCESS_KEY >> ~/.aws/credentials
      - echo '[s3_iam]' >> ~/.aws/credentials
      - echo 'aws_access_key_id = '$AWS_S3_ACCESS_KEY_ID >> ~/.aws/credentials
      - echo 'aws_secret_access_key = '$AWS_S3_SECRET_ACCESS_KEY >> ~/.aws/credentials
      - berks package cookbooks.tar.gz
      - aws --profile s3_iam s3 cp cookbooks.tar.gz s3://rails-application-cookbooks/
      - aws --profile opsworks_iam opsworks --region us-east-1 create-deployment --stack-id $STACK_ID --command "{\"Name\":\"update_custom_cookbooks\"}"
      - ruby ./circleci/scripts/update_stack.rb
circleci/scripts/install_berks_cookbooks.sh

BerkshelfコミュニティCookbookインストールスクリプト。

#!/bin/bash
echo "install berkshelf cookbooks."
cd berks_cookbooks
berks vendor cookbooks
circleci/scripts/update_stack.sh

config/custom_json.ymlをjsonに変換してStackのCustom Jsonにアップデートするスクリプト。

require 'json'
require 'yaml'
puts 'update stack custom json.'
custom_json = "'#{YAML.load_file('config/custom_json.yml').to_json}'"
system("aws --profile opsworks_iam opsworks --region us-east-1 update-stack --stack-id #{ENV['STACK_ID']} --custom-json #{custom_json}")
config/custom_json.yml

Json形式でも良いかもしれませんが、yaml形式のほうが見やすいかと思い、yamlからJsonに変換するようにしました。
Custom Jsonは、アプリ固有の値などを設定し、カスタムレシピ内でCustom Jsonにセットした値を使用する設計です。
例えばAPIサーバではCORS対応、CMSサーバではimagemagickをインストールする、などです。
Custom Jsonの値だけを修正すれば別のRailsアプリケーションが配備されたスタックでもこのCookbookをコピーするだけで使うことが出来るようにしました。

---
  stack:
    stack_name: "rails_application_stack"
    layers:
      -
        layer_name: "web-server"
        deploy_layer_name: "deploy-server"
        app_name: "web_application"
        ruby:
          versions:
            -
              version: "2.3.0"
              global: "true"
      -
        layer_name: "cms-server"
        deploy_layer_name: "deploy-server"
        app_name: "api_application"
        nginx:
          cors: true
        ruby:
          versions:
            -
              version: "2.3.0"
              global: "true"
        imagemagick: true
      -
        layer_name: "deploy-server"
        ruby:
          versions:
            -
              version: "2.3.0"
              global: "true"

CircleCIに環境変数設定

AWS_OPS_WORKS_ACCESS_KEY_ID AWS_OPS_WORKS_SECRET_ACCESS_KEY AWS_S3_ACCESS_KEY_ID AWS_S3_SECRET_ACCESS_KEY
S3のIAMユーザー、OpsWorksのIAMユーザを作成しておき、上記にセットします。

以上でGitHubにプッシュし、OpsWorksにCookbookを配備するところまでが出来ました。

次回へ続く

今回はCookbookをOpsWorks上に配置するところまでをつらつらと書きました。
全体設計やレシピを書く前にやったことなどを次回書きたいと思います。
AWSを活用したスケーラブルなサービス構築をお考えの際は、是非MMMにお声がけ下さい!

追記 : 記事をアップしました。
-> Chef12に対応したAWS OpsWorksにRailsアプリをデプロイする②

このエントリーをはてなブックマークに追加