Git submoduleを含むサービスをAWS CodePipelineで扱う

新しい職場もあっという間に半年経ちますが、おかげさまで元気で毎日忙しい日々を過ごしているAWS初心者のわたなべです。

AWS CodePipeline

現在いくつかの案件を並行して進めているのですが、その中で知人が在籍している某スタートアップのインフラ改善支援で利用しているサービスがAWS CodePipelineです。

aws.amazon.com

CodePipelineは、Jenkinsやcircleciのように、CI/CDを実現するためのサービスで、AWS謹製ということでAWSの他のサービスと連携しやすいのが特徴かと思います。

B/G デプロイメント

今回の要望として、ECS Fargeteを使用してBlue/Greenデプロイメントを実現するということで、aws-examplesにある以下のサンプルを参考にしています。

github.com

このリポジトリfargate ブランチがFargate上でB/Gデプロイメントを実現するサンプルで、ざっくり以下のような流れでB/Gデプロイメントを実現しています。

  • Githubの更新
  • コンテナのビルド
  • ECRにPush
  • Deploy先の検出
  • Fargateにデプロイ - Green側のTaskを更新
  • 承認
  • FargateタスクのSwitch

今回の背景

今回対象になるサービスは、以下のような形でいくつかのAPIをGit submoduleとして構成されています。

  • example-center

全体を管理する、example-centerの更新をトリガーに配下のAPI群をBuild => Deployしたいのですが、AWS CodePipelineではgit submoduleはサポートされていません。

パイプラインのエラー: GitHub ステージ自分のソースは Git サブモジュールが含まれているが、AWS CodePipeline 初期化されていません

上記FAQに、 可能な変更: 別スクリプトの一部として直接 GitHub リポジトリをクローンすることを検討してください。 と書かれているので実際に試してみました。

今回やってみたこと

git submodule updateで行けるだろうとおもっていたのですが、Buildコンテナには .git ディレクトリが含まれていないので無理でした。仕方がないので pre_build フェーズで以下のような形でsubmoduleを一つづつcloneすることにしました

                - git clone "https://$GITHUB_TOKEN@github.com/bar/foo-api.git"

実際のビルド処理、pushする処理及びartifactとして次の処理に渡すbuild.jsonは、以下のようなスクリプトで生成するようにしてみました。このスクリプトでは、変更されていないsubmoduleのビルドを省くために、コンテナのタグにgitのhashを使うよう変更しています。

#!/bin/bash

# usage 
# create-build-script.sh REPOSITORY_URL REPOSITORY_NAME

services=(
    "foo-api" # 実際には複数のサブモジュールを定義している
)

declare -A tags=()

echo "#!/bin/bash" > /tmp/build.sh
echo "#!/bin/bash" > /tmp/push.sh

for service in "${services[@]}" ; do
    if [ ! -d "./${service}" ]; then
        echo "[${service}] Service directory not found"
        continue
    fi

    hash=`git -C ./${service} log --pretty=%H | head -n 1`

    echo "[${service}] Current tag => $2:${hash}"
    result=`aws ecr list-images --repository-name $2 | grep $hash | wc -l`

    if [ $result -eq 0 ]; then
        echo "docker build --tag $1:${hash} ./${service}"
        
        echo "echo \"docker build --tag $1:${hash} ./${service}\"" >> /tmp/build.sh
        echo "docker build --tag $1:${hash} ./${service}" >> /tmp/build.sh

        echo "echo \"docker push $1:${hash}\"" >> /tmp/push.sh
        echo "docker push $1:${hash}" >> /tmp/push.sh
    else
        echo "[${service}] Latest version image exists, build process was skipped."
    fi

    tags[$service]=$hash
done

for key in "${!tags[@]}"; do
    printf '%s\0%s\0' "$key" "${tags[$key]}"
done |
jq -Rs '
  split("\u0000")
  | . as $a
  | reduce range(0; length/2) as $i 
      ({}; . + {($a[2*$i]): ($a[2*$i + 1]|fromjson? // .)})' > /tmp/build.json

このスクリプトで使用している jq ですが標準のビルド環境でインストールすると1.3という少し古いバージョンがインストールされるので、 install フェーズで強引にjq-1.5をインストールしています。

また、スクリプトではawsコマンドを使用して、ecrのイメージ一覧を取得しているので、 CodeServiceRollecr:ListImages 権限を追加する必要があります。

...

そして、最終的なBuild処理の定義はこんな感じです。(実際に使用しているものからは少し変えています...)

  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Artifacts:
        Type: CODEPIPELINE
      Source:
        Type: CODEPIPELINE
        BuildSpec: |
          version: 0.1
          phases:
            install:
              commands:
                - apt-get update && apt-get -y install python-pip git wget jq
                - pip install --upgrade python
                - pip install --upgrade awscli
                - wget https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64
                - chmod +x jq-linux64
                - mv jq-linux64 $(which jq)
            pre_build:
              commands:
                - printenv
                - git clone "https://$GITHUB_TOKEN@github.com/bar/foo-app.git"
                # - echo -n "$CODEBUILD_LOG_PATH" > /tmp/build_id.out
                # - printf "%s:%s" "$REPOSITORY_URI" "$(cat /tmp/build_id.out)" > /tmp/build_tag.out
                # - printf '{"tag":"%s"}' "$(cat /tmp/build_id.out)" > /tmp/build.json
                - $(aws ecr get-login)
                - bash create-build-shript.sh "$REPOSITORY_URI" "$REPOSITORY_NAME"
            build:
              commands:
                # - docker build --tag "$(cat /tmp/build_tag.out)" .
                - bash /tmp/build.sh
                - cat /tmp/build.json
            post_build:
              commands:
                # - docker push "$(cat /tmp/build_tag.out)"
                - bash /tmp/push.sh
          artifacts:
            files: /tmp/build.json
            discard-paths: yes
      Environment:
        ComputeType: "BUILD_GENERAL1_SMALL"
        Image: "aws/codebuild/docker:1.12.1"
        Type: "LINUX_CONTAINER"
        EnvironmentVariables:
          - Name: AWS_DEFAULT_REGION
            Value: !Ref AWS::Region
          - Name: REPOSITORY_URI
            Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${Repository}
          - Name: REPOSITORY_NAME
            Value: !Ref Repository
          - Name: GITHUB_TOKEN
            Value: !Ref GitHubToken
      Name: !Sub ${AWS::StackName}-codebuildproject
      ServiceRole: !Ref CodeBuildServiceRole

この設定で作成される build.json はサンプルとは異なりますのでこのデータを受け取る deployer.py の該当箇所も修正する必要があります。

def get_build_artifact_id(build_id):
... snip
        print(objbuild['foo-api']) """サンプルでは 'tag' になっているので変更
        return objbuild['foo-api']

実際は複数のsubmoduleをデプロイする場合には、他にもいろいろと修正が必要ですが、ひとまずこのような形でgit submoduleに対応できそうです。

AWSはあまり使ったことがなく、試行錯誤しているところなのでもっといい方法がないか含め引き続きいろいろ試してみます。

Ansibleでプロビジョニング時にMackerelのグラフアノテーションを追加する

相変わらず、忙しいのに仕事と関係ないコード書いたり、ブログ書いたりしたくなって困っているわたなべです。

弊社ではほとんどの案件でサーバーの環境設定にAnsible、デプロイにはCapistranoかAnsistrano、監視にはMackerelを使っています。

で、表題の通りなのですが、デプロイやプロビジョニングしたタイミングを残しておきたいと思いMackerelのグラフアノテーション機能を使うことにしました。

mackerel.io

mackerel.io


Capistranoでデプロイ時にMackerelのグラフアノテーションを追加する方法については、以下のブログでも紹介されている通り簡単に設定できます。

blog.a-know.me

とことが、Ansibleでグラフアノテーションを方法を探っていたのですが良さそうなものが見つかりませんでした。仕方がないので公式のSlack通知モジュールを参考に自作してみました。

gist.github.com

公式のSlack通知モジュールと同じイメージで使えると思います。なかなかいい感じ。

f:id:kaz_29:20180314064920p:plain

from_time にプロビジョニング開始時刻をセットしたいんだけどうまい方法が見つからなかったので引き続き調査する。

basercmsコンテナをciecleciで自動ビルドするようにしました

先日の以下の記事でbasercmsのコンテナを作った話を書きました。

kaz29.hatenablog.com

で、いくつか中の人に指摘された点をなおして一安心していたら、新しいバージョンがリリースされていました。

basercms.net

ということで、baserCMS 4.0.9に対応させたのですが修正内容はバージョン番号を変えるだけです。

github.com

これだけだとちょっとつまらないので、circleciを使って自動でコンテナを作成するようにしました。

circleci.com

弊社でもcircleciは以前から使っていましたが、今年7月に正式リリースされたversion 2はお試し程度であまり使っていませんでした。

circleci v2では、設定ファイルなども一新されて、Dockerを正式にサポートされました。

コンテナをビルドするだけなら設定はかなり簡単で、.circleci/config.yml にこんな設定を書くだけです。

version: 2
jobs:
  build:
    branches:
      only:
        - master
    machine: true
    environment:
      - DOCKER_IMAGE_REPO: kaz29/basercms
    working_directory: ~/basercms
    steps:
      - checkout
      - run: docker login -u $DOCKER_USER -p $DOCKER_PASS
      - run: docker pull $DOCKER_IMAGE_REPO:latest
      - run: docker build -t circleci --cache-from $DOCKER_IMAGE_REPO:latest .
      - run: docker tag circleci $DOCKER_IMAGE_REPO:latest
      - run: docker push $DOCKER_IMAGE_REPO:latest

これで、masterを更新すれば自動でコンテナが作られるのでかなり楽ですね。自社で構築したJenkinsでbuildしているのも合間を見て移転しようかと思ってます。

参考URL

qiita.com

baserCMSをdocker化したついでに、Web App for Containers + Azure Database for MySQLでも動くようにした話。

少し前に、baserCMSの中の人と飲みに行った時に、baserCMSをdockerで動くようにしたらいいよ!と言ったところ、じゃぁかずさんやってねヨロシク!と言われてしまったので、作りました。

https://hub.docker.com/r/kaz29/basercms/

github.com

で、最近Web App for Containersで本番環境を運営していたりするので、Web App for Containers + Azure Database for MySQLでも動くようにちょっと手を入れたのでそのあたりのことについてちょっとまとめておきます。

Azure Database for MySQL

Azure Database for MySQLは、現状(2017/11/24現在)まだpreviewなのですが、ManagedなDBサービスです。他に、Azure Database for PostgreSQLというのもあって、弊社では最近こちらの方を使っています。

azure.microsoft.com

PostgreSQLの方は普通に使い始めているので、知見を貯めるためにあえてMySQLを試してみることにしました。

作成自体は、Azure portalからぽちぽちすれば簡単に作れますのでざっくり省略します。

basercms用のdatabaseを作成

Azure Database for MySQLを作成後に、databaseを作成するには、Cloud Shellを使うのがラクかなと思います。 portal右上の下記のボタンで起動できます。

f:id:kaz_29:20171124124535p:plain

Cloud Shellが起動したら早速繋いでみます。と、こんなエラーがでると思います。

watanabe@Azure:~$ mysql -h [ホスト名] -u ログインユーザ名 -P 3306 -p
Enter password:
ERROR 9000 (HY000): Client with IP address 'XXX.XXX.XXX.XXX' is not allowed to connect to this MySQL server.

Azure Database for MySQLは、現状だとGlobalなIPでアクセスすることになるので、IPアドレスベースのアクセス制限がかかっています。Cloud ShellからアクセスしたIPアドレスが表示されていると思うのでこれを、「接続のセキュリティ」画面で「開始IP」、「終了IP」に入力して保存します。

f:id:kaz_29:20171124125115p:plain

これで、MySQLに接続できるので、適宜databaseを作成してください。

PHPからAzure Database for MySQLへの接続

Azure Database for MySQLは、デフォルトではsslでの接続のみが許可されています。設定で外すこともできますが、セキュリティ的にもsslで接続した方が良いでしょう。そのあたりのことは下記の公式ドキュメントにも書かれています。

docs.microsoft.com

が、baserCMSでは、DBにつなぐ時にはPDOを利用しているのですが、そのあたりのことはあまり記述を見つけられませんでした。 色々調べた結果、以下のような設定で接続できることを確認しました。

<?php

$dsn = "mysql:dbname=basercms;host=ホスト名;port=3306";
$flags = [
    PDO::MYSQL_ATTR_SSL_CA => '/usr/local/etc/BaltimoreCyberTrustRoot.crt.pem', // <- 上記公式ドキュメントに解説のある証明書をダウンロードして指定
    PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false,
];

try {
    $pdo = new PDO($dsn, 'ユーザー名', 'パスワード', $flags);
    echo 'Connected' . PHP_EOL;
} catch (Exception $e) {
    echo "Error : " . $e->getMessage() . PHP_EOL;
}

baserPHPでssl接続のサポート

残念ながら、現状、デフォルトのbaserCMSではMySQLヘのssl接続はサポートされていません。しかし、baserCMSはCakePHP2で構築されていて、CakePHP2のORMはPDOを利用しているので、ちょっとした修正で対応出来ます。

詳細な解説はしませんが、kaz29/basercms:webapp_on_container_mysql とタグをつければssl接続に対応したパッチずみのbaserCMS-4.0.8を利用できるようにしてありますのでこちらを利用して下さい。

パッチの詳細に興味のある方は、上記リポジトリの、webapp_on_container_mysqlブランチのpatchファイルを確認してみて下さい。

https://github.com/kaz29/basercms-on-docker/blob/webapp_on_container_mysql/BcManagerComponent.diff

https://github.com/kaz29/basercms-on-docker/blob/webapp_on_container_mysql/InstallationsController.diff

Web App for Containersの作成

Web App for Containersの作成も、portalでぽちぽちすれば簡単に設定できるので、詳しく解説しませんが、コンテナーの構成で以下のようにmysql対応のコンテナイメージを指定してください。

f:id:kaz_29:20171124141333p:plain

Web App for ContainersからAzure Database for MySQLへの接続を許可

しばらく待つと、おなじみのbaserCMSのインストール画面がでると思います。が、この状態ではまだ作成したコンテナからMySQLヘの接続が出来ないので、先ほどと同じようにAzure Database for MySQL「接続のセキュリティ」画面でコンテナからの接続を許可する設定を追加します。

Web App for Containersから外部に出て行く際のIPアドレスは、設定画面の「プロパティ」内の送信 IP アドレスという項目で確認することが出来ます。ここに表示されているIPアドレスからの接続を許可します。

あとはインストーラーにDBの接続パラメータを設定して行けば設定が完了すると思います。

portalからコンテナへのssh接続

Web App for Containersでは、Azure portalからコンテナ内部にsshで接続してコンテナの中を覗くことができます。安定して動いてしまえば使うこともほどんどない機能なのですが、独自のイメージを作成直後はこの機能を有効にしておくことでコンテナ内の状況を把握して対応できるので重宝します。

今回紹介したコンテナはこのssh接続に対応しているので、利用する場合は、Dockerfileを参考にしてみて下さい。

注意

今回解説した方法で、簡単にbaserCMSを試すことが出来ますが、コンテナを再起動すると初期状態に戻ってしまいます。 継続的に利用する場合は、手元で自前のイメージをbuildするなどしてご利用くださいませ。

Azure FunctionsをMacで動かせるようになったよ!

最近、仕事でもプライベートでも色々あってワタワタしているわたなべです。

めっちゃ忙しい最中だったのですが、昨日は以下のイベントに参加してきました。

www.meetup.com

このイベントの中で、MSの牛尾さんがMacにインストールしたAzure Functionsでデモをしていました。durableの話など、とても面白い話をたくさん聞けたのですが、あまりに盛り上がりすぎて予定したHacktimeの時間が短くなってしまいました。

何か作るほどの時間はなさそうだったので、自分のMacでFunctionsを動かして見ることにしました。

Functionsをローカルで動かそうと、検索をするとおそらく以下の日本語版のページが見つかると思います。

docs.microsoft.com

しかし、ここで紹介されている手順はWindows環境用のものです。で、牛尾さんに聞いたところ同じページの英語版を紹介されました。

docs.microsoft.com

日本語版はまだ更新されていなくて、英語版には .NET Core 2.0を使うVersion 2.x環境のインストール方法の解説が追加されています。

VSCodeはインストール済みだったので、こちらの情報を元に .NET Core 2.0とazure-functions-core-toolsをインストールして、チュートリアル通りにFunctionを作っていきます。ざっくりこんな感じ

$ npm install -g azure-functions-core-tools@core
$ func init MyFunctionProj
$ cd MyFunctionProj/
$ func new --language JavaScript --template "Http Trigger" --name MyHttpTrigger
$ func host start

無事起動はできているみたい...

$ func host start

                  %%%%%%
                 %%%%%%
            @   %%%%%%    @
          @@   %%%%%%      @@
       @@@    %%%%%%%%%%%    @@@
     @@      %%%%%%%%%%        @@
       @@         %%%%       @@
         @@      %%%       @@
           @@    %%      @@
                %%
                %

[2017/09/29 13:00:33] Reading host configuration file '/Users/kaz/dev/MyFunctionProj/host.json'
[2017/09/29 13:00:33] Host configuration file read:

...snip

Http Functions:

        MyHttpTrigger: http://localhost:7071/api/MyHttpTrigger

[2017/09/30 0:15:13] Found the following functions:
[2017/09/30 0:15:13] Host.Functions.MyHttpTrigger

...

ということで表示されているURLを叩いてみると以下のエラーが...。

      Start Process: node  --inspect=5858 "/Users/kaz/.azurefunctions/bin/workers/node/dist/src/nodejsWorker.js" --host 127.0.0.1 --port 51541 --workerId 23e4bc8d-b96c-44e5-9d5f-9daf1855667a --requestId 21e21d6c-bb26-429c-a356-3ec38836fc78
[2017/09/29 13:00:54] A ScriptHost error has occurred
[2017/09/29 13:00:54] The operation has timed out.
info: Worker.Node.23e4bc8d-b96c-44e5-9d5f-9daf1855667a[0]
      Unable to open devtools socket: address already in use
fail: Worker.Node.23e4bc8d-b96c-44e5-9d5f-9daf1855667a[0]
      Worker encountered an error.

色々調べてみたのですが、原因がわからず牛尾さんにヘルプをかけると以下のブログを紹介されました。

blogs.msdn.microsoft.com

9/25に公開されたばかりのページで、Node 8.5以降の場合と、それ以前のバージョンを使っている場合の構築方法が解説されています。 私の環境は 6.9系だったので、この解説にしたがって以下のように追加で作業します。

npm i -g node-pre-gyp
cd ~/.azurefunctions/bin/workers/node/grpc
node-pre-gyp install

再度チャレンジすると無事動きました!

デバッガを起動して、ブレイクポイントで止めてみたのら以下のような感じ。

f:id:kaz_29:20170930092733p:plain

しっかりブレイクポイントで止まって、変数の中身もしっかり見れています。これは捗ります!昨日のイベントで紹介されていて、凄く良さげだった durable function も手元で試せますね。

今のところ、C#とNodeのランタイムだけのようですが、追々他のランタイムも追加されるでしょう。

仕事がだいぶ忙しかったので、キャンセルしようかと直前まで悩んでいたのですが、参加して本当によかったです!ということで、仕事に戻ります(><)

Azure FuncsionsでChatworkへの書き込みをSlackに通知させてみた

今開発中の案件で、お客さんの使っているチャットツールがChatworkだった。前からお付き合いのある担当者さんは弊社のSlackに招待してそこでやり取りしているのですが、他のメンバーさんにSlackを強制するのが若干無理目な状況でした。Chatworkは常用していないので、どうしても書き込みを見落としてしまいがち…。ということで、前々から興味があったAzure FunctionsでSlackに通知するようにしてみました。

Azure FunctionsはMicrosoftのサーバーレスなコンピューティングサービスです。FaaSってやつですね。

関数を作成

今回は、タイマーで5分おきに起動して、Chatworkの指定したチャットに新着があればSlcakに通知することにするので、Function App作成後の初期画面で、タイマー、Javascriptを選んでこの関数を作成するを選んで関数を作成します。

f:id:kaz_29:20161207154832p:plain

この状態で、5分おきに自動的に関数が実行されるように設定されています。

環境変数の設定

今回の機能では、ChatworkのAPIトークンなどを扱います。これらをコードに埋め込みたくないので、以下の項目を環境変数として設定します。

変数名 説明
CW_CHATID 監視対象のChatID
CW_TOKEN ChatworkのAPIトークン
SLACK_WEBHOOK_URI SlackのIncomingWebHookのURI
SLACK_CHANNEL 投稿先のチャンネル名(#含む)

設定は、画面左下のFunction App の設定 -> アプリケーション設定の構成で表示されるアプリ設定で行います。以下のような感じ。

f:id:kaz_29:20161207154902p:plain

使用するパッケージのインストール

今回は以下の2つのnpmパッケージを使用するので、以下の手順でインストールします。

Function App の設定 -> Kudu に移動 を選択してKuduのコンソールで以下のように入力します。

D:\home>cd site/wwwroot/<作成した関数名>
D:\homesite/wwwroot/TimerTriggerJS1> npm install request slack-node 

package.jsonを用意してインストールする方法もあるようなのですが、今回は簡易的にこんな感じでやってしまいました。

コードを作成

これで前準備が整いましたので、関数を作っていきます。以下のコードを作成した関数の開発を選択しコードエディタに貼り付けて保存すればOKです。


Azure functions Chatwork to Slack


問題なく設定ができていれば、タイマーで実行されるのを待つか実行ボタンを押せば以下のような形でSlackに通知されます。

f:id:kaz_29:20161207154951p:plain

エラー処理とか超適当ですが、これでChatworkへの書き込みも見落とさないでしょう。

今回、Azure Functionを初めて使いましたが、なかなか面白いですねー。何かいいケースがあったら使いたいなと思います。

SQL Server on Linuxを入れてみた

Linuxに対応したSQL Serverの時期バージョンがパブリックプレビューになったので早速入れてみた。

SQL Server v.Next—SQL Server on Linux | Microsoft

インストール環境

今回は手元のmacOS Sierra + Vagrant 1.8.7 環境に構築してみました。今後何かで使うかもしれないので、Ansibleのplaybookを作ったので以下を見てもらえれば。。。

github.com

使い方

GitHub - kaz29/mssql-vagrant にも書きましたがこんな感じで。ansible-localを使っているのでVagrantが動く環境なら問題なく起動できると思います。

$ git clone https://github.com/kaz29/mssql-vagrant.git
$ cd mssql-vagrant
$ vagrant up
...
$ vagrant ssh


$ sudo /opt/mssql/bin/sqlservr-setup
Microsoft(R) SQL Server(R) Setup

You can abort setup at anytime by pressing Ctrl-C. Start this program
with the --help option for information about running it in unattended
mode.

The license terms for this product can be downloaded from
http://go.microsoft.com/fwlink/?LinkId=746388 and found
in /usr/share/doc/mssql-server/LICENSE.TXT.

Do you accept the license terms? If so, please type "YES": YES

Please enter a password for the system administrator (SA) account:
Please confirm the password for the system administrator (SA) account:

Setting system administrator (SA) account password...

Do you wish to start the SQL Server service now? [y/n]: y
Do you wish to enable SQL Server to start on boot? [y/n]: y
Created symlink from /etc/systemd/system/multi-user.target.wants/mssql-server.service to /lib/systemd/system/mssql-server.service.
Created symlink from /etc/systemd/system/multi-user.target.wants/mssql-server-telemetry.service to /lib/systemd/system/mssql-server-telemetry.service.

Setup completed successfully.

$ sqlcmd -S localhost -U SA -P '<YourPassword>'
1> SELECT Name from sys.Databases;
2> GO
Name
--------------------------------------------------------------------------------------------------------------------------------
master
tempdb
model
msdb

(4 rows affected)
1> quit

一点注意点としては、SQL Serverはメモリが3.25G以上必要ということで、Vagrantに4Gのメモリを割り当てる様に設定されていますので、MBAとかMacBookだと厳しいかもしれません。

とりあえず、サクッと動いたので今後色々試してみたいと思います。