Elasticbeanstalkのデプロイ時に走る処理を本番環境と開発環境で切り替えたかった。。

お久しぶりです。chikamaです。

皆さんAWSのElasticbeanstalk使っていますか?個人的にはすごく好きです。
デプロイ時にやりたいことを.ebextensionsフォルダ以下に設定ファイルとして
定義しておくと自動でやってくれるので、事前に全部書いておくだけで良いというのが魅力的です。

ただ、毎回思っていたんです。

.ebextensions内で本番と開発によってやることを切り替えたい!!!

ソースコードをcodecommit上にプッシュすることでデプロイされるように組んでいるのですが、
開発のブランチから本番ブランチにマージするときに毎回設定ファイルだけ書き換えていました。
非常にめんどくさいしブランチごとに必ず差分が発生するのが嫌で嫌で・・・。

なので今回は、開発→本番にマージするだけで設定ファイルもいじらずデプロイが完了し、
最終的なソースコード全体に差分が出ないことを目指しました!!!


目的

・開発ブランチ→本番ブランチへのマージのみでデプロイが完結すること
・Elasticbeanstalkの設定ファイル含めて両者に差分がでないこと


前提とやりたいこと

前提として、現環境ではCodePipeline上でCodeCommitとElasticbeanstalkをつなげて、
ソースをプッシュするのをトリガーとしてデプロイが開始されるように構築しています。
ソースをプッシュ後、ロードバランサーに紐づくEC2インスタンスに変更をデプロイしていく流れです。
デプロイ時に大きくやっていることが4つ。
・nodeのインストール、npmパッケージのインストールなどで環境を整える
・ソースコードの変更を反映する
・EC2インスタンスに用意したEIPを紐付ける
・npm run dev(prod)で環境を立ち上げる

環境別に自動で切り替えたかったのは上記4つのうち、後者の2つになります。

・EC2インスタンスに紐付けるEIPのリストを環境ごとに切り替えたい!
・開発環境にデプロイするときはnpm run dev、本番環境ではnpm run prodにしたい!

設定方法の詳細に関しては以下の公式ドキュメントを参照してください。
設定ファイル による高度な環境のカスタマイズ
Linux サーバーでのソフトウェアのカスタマイズ


とりあえずやってみた

当初、設定ファイルには特に環境変数なんかも考慮せずに環境ごとに
設定ファイルだけ書き換えてプッシュするという力技を使っていました。
環境変数使えば簡単に実現できるのではないか・・・。やってみました。

※参考として載せているスクリプトは一部省略してあります。


1.EIPの紐付け

まずは、
・EC2インスタンスに紐付けるEIPのリストを環境ごとに切り替えたい!
の問題を解決していきます。

環境変数の設定

Elasticbeanstalk設定画面

/.ebextensions/01_eip.config

files:
  "/usr/local/bin/auto_set_eip.sh":
          mode: "000755"
          owner: root
          group: root
          content: |
              #!/bin/bash
              
              # 環境変数取得
              echo $EB_ENV

              # 自身のインスタンスIDとリージョンを取得
              instance_id=$(wget -q -O - http://169.254.169.254/latest/meta-data/instance-id)
              region=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone | sed -e 's/.$//')
              export AWS_DEFAULT_REGION=${region}

              # 自身にElasticIPが関連づいているかどうか 関連づいていれば1 ついていなければ0
              CHECK=$(aws ec2 describe-addresses --filter "Name=instance-id,Values=${instance_id}" --query 'length(Addresses[].PublicIp[])')

              # EB_ENVがdevelopmentなら開発環境 それ以外なら本番環境
              if [ "development" == "$EB_ENV" ]; then 

                echo 'execute set_eip_development'
                echo $CHECK

                while [ "$CHECK" != "1" ]
                do

                  sleep $(($RANDOM % 5))
                     
                  bash set_eip_development.sh
                  wait
                  CHECK=$(aws ec2 describe-addresses --filter "Name=instance-id,Values=${instance_id}" --query 'length(Addresses[].PublicIp[])')

                  sleep 2

                done

              else 

                echo 'execute set_eip_prod'
                echo $CHECK

                while [ "$CHECK" != "1" ]
                do

                  sleep $(($RANDOM % 5))

                  bash set_eip_prod.sh
                  wait
                  CHECK=$(aws ec2 describe-addresses --filter "Name=instance-id,Values=${instance_id}" --query 'length(Addresses[].PublicIp[])')

                  sleep 2

                done

              fi

  "/usr/local/bin/set_eip_development.sh":
          mode: "000755"
          owner: root
          group: root
          content: |
              #!/bin/bash
              # 開発環境

              eip_alloc_ids="eipalloc-****** eipalloc-**** eipalloc-******"

              #### Omitted. Processing to link EIP development ####

  "/usr/local/bin/set_eip_prod.sh":
          mode: "000755"
          owner: root
          group: root
          content: |
              #!/bin/bash
              # 本番環境

              eip_alloc_ids="eipalloc-xxxxx eipalloc-xxxx eipalloc-xxxxxx"

              #### Omitted. Processing to link EIP production ####


commands: 
  01_set_eip_instance:
    cwd: /usr/local/bin
    command: 'sh auto_set_eip.sh'

↑のように設定ファイルを書き、デプロイを実行していきます・・・。

EIP紐付け処理の結果・・・失敗!!!


[1.EIPの紐付け] – 失敗の原因

.ebextensions上のcommandタイミングでは、環境変数が読み取れなかったため
echo $EB_ENVが空白だった。。。

結論から言うと、Elasticbeanstalkで設定した環境変数はcontainer_commandsのタイミングでないと参照できない。
環境ごとに処理を切り替えたい場合はcontainer_commandsのタイミングで実行する必要がある。


[1.EIPの紐付け] – 再挑戦

/.ebextensions/01_eip.configを以下のように変更

– commands:
+ container_commands
01_set_eip_instance:
cwd: /usr/local/bin
command: ‘sh auto_set_eip.sh’

EIP紐付け処理・・・成功!!!


2.npmスクリプト

「1.EIPの紐付け」がうまくいきましたので、次は
・開発環境にデプロイするときはnpm run dev、本番環境ではnpm run prodにしたい!
を解決していきます。
すでにElasticbeanstalk上で環境変数EB_ENVが設定されている前提で進めます。

02_node.config

commands:
    00_node_repository_add:
        cwd: /tmp
        test: '[ ! -f /usr/bin/node ] && echo "node not installed"'
        command: 'curl -sL https://rpm.nodesource.com/setup_8.x | sudo bash -'

    01_node_install:
        cwd: /tmp
        test: '[ ! -f /usr/bin/node ] && echo "node not installed"'
        command: 'yum install -y nodejs gcc-c++ make'

    02_n_install:
        cwd: /tmp
        test: '[ ! -e /usr/bin/n ] && echo "node not updated"'
        command: 'npm install -g n && n stable'

files:
    "/opt/elasticbeanstalk/hooks/appdeploy/pre/99_npm_install.sh":
        mode: "000755"
        owner: root
        group: root
        content: |
            #!/bin/sh
            EB_APP_STAGING_DIR=$(/opt/elasticbeanstalk/bin/get-config container -k app_staging_dir)
            cd $EB_APP_STAGING_DIR
            echo $EB_APP_STAGING_DIR

            npm cache verify

            npm install

    "/usr/local/bin/99_npm_run.sh":
        mode: "000755"
        owner: root
        group: root
        content: |
            #!/bin/sh
            EB_APP_STAGING_DIR=$(/opt/elasticbeanstalk/bin/get-config container -k app_staging_dir)
            cd $EB_APP_STAGING_DIR
            echo $EB_APP_STAGING_DIR

            echo $EB_ENV
            
            npm config set scripts-prepend-node-path true
            if [ "development" == "$EB_ENV" ]; then 
              echo 'execute npm run dev container_commands'
              npm run dev
            else 
              echo 'execute npm run prod container_commands'
              npm run prod
            fi


container_commands: 
  99_npm_run_command:
    cwd: /usr/local/bin
    command: '99_npm_run.sh'

↑のように設定ファイルを書き、デプロイを実行していきます・・・。

npmスクリプト切り替えの結果・・・失敗!!!


[2.npmスクリプト] – 失敗の原因と解決策

これにはいくつかの原因が重なっていたためちょっとわかりにくいですが、
結論から言うと権限とnpmバージョンの差異から発生した失敗でした。

1.権限問題
container_commandsのタイミングではroot権限でないとnpmコマンドが使えなかった。


“/usr/local/bin/99_npm_run.sh”で実行するnpmコマンドを”sudo npm run dev”に変更

・npmバージョンの差異
“/usr/local/bin/99_npm_run.sh”でのコマンドのみroot権限での実行をしたところ、
通常権限のnodeバージョンが12、root権限では8になっていることが判明。
どうやら事前にinstallしていたnodeはroot権限、通常権限とはpathが違う。
依存関係のエラーで失敗。。。


全npmコマンドを”sudo”を使ってroot権限で実行し、バージョンを揃えた。


[2.npmスクリプト] – 再挑戦

02_node.configを以下のように変更して再挑戦

02_node.config

commands:
    00_node_repository_add:
        cwd: /tmp
        test: '[ ! -f /usr/bin/node ] && echo "node not installed"'
        command: 'curl -sL https://rpm.nodesource.com/setup_12.x | sudo bash -'

    01_node_install:
        cwd: /tmp
        test: '[ ! -f /usr/bin/node ] && echo "node not installed"'
        command: 'yum install -y nodejs gcc-c++ make'

    02_n_install:
        cwd: /tmp
        test: '[ ! -e /usr/bin/n ] && echo "node not updated"'
        command: 'npm install -g n && n stable'

files:
    "/opt/elasticbeanstalk/hooks/appdeploy/pre/99_npm_install.sh":
        mode: "000755"
        owner: root
        group: root
        content: |
            #!/bin/sh
            EB_APP_STAGING_DIR=$(/opt/elasticbeanstalk/bin/get-config container -k app_staging_dir)
            cd $EB_APP_STAGING_DIR
            echo $EB_APP_STAGING_DIR

            sudo npm cache verify

            sudo npm install

    "/usr/local/bin/99_npm_run.sh":
        mode: "000755"
        owner: root
        group: root
        content: |
            #!/bin/sh
            EB_APP_STAGING_DIR=$(/opt/elasticbeanstalk/bin/get-config container -k app_staging_dir)
            cd $EB_APP_STAGING_DIR
            echo $EB_APP_STAGING_DIR

            echo $EB_ENV
            
            npm config set scripts-prepend-node-path true
            if [ "development" == "$EB_ENV" ]; then 
              echo 'execute npm run dev container_commands'
              sudo npm run dev
            else 
              echo 'execute npm run prod container_commands'
              sudo npm run prod
            fi


container_commands: 
  99_npm_run_command:
    cwd: /usr/local/bin
    command: '99_npm_run.sh'

npmスクリプト切り替えの結果・・・成功!!!


まとめ

いかがでしたでしょうか?

Elasticbeanstalkはデプロイが失敗するとそのときのログがとれなかったり、npm周りはバージョンや依存関係、権限、path・・・わかりづらいエラーが発生しがちなため、”統一”という考え方は徹底すべきだなと改めて勉強になりました。

また、Elasticbeanstalkのデプロイ時の挙動を学ぶ良い機会になりました。
まさか参照タイミングが決まっているとは考えてもいなかったのですが、さらにタイミングによってnpm周りの権限問題が発生するとは想像もしていませんでした笑

これまでは環境ごとに設定ファイルを都度変えるというのがネックだと感じていたのですが、今回の方法であれば環境別にやりたいことがあっても同一のソースコードを、ブランチを分けて管理するだけで済みます。
デプロイ時に接続先のDBや環境ファイルを自動で切り替えたりもできます。
設定は複雑ではありますが、一度作ってしまえば他のプロジェクトへの横展開もやりやすいと思います。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

Bitnami