Azure Kubernetes Service (AKS)の構築とAzure DevOpsを使用したCI/CD pipelineの作成

昨年11/9,10に開催された寺田さん(https://twitter.com/yoshioterada)によるMVP受賞者向けAKSハッカソンに参加したご縁で、1/21-25にAKS/Azure DevOpsのワークショップを開催していただきました。

ここでは、AKSクラスタ構築の概要とAKS、Azure DevOpsを利用したCI/CD pipeline構築の一例を紹介したいと思います。なお、Kubernetes,AKS,Azure DevOps全て日々成長を続けていますのでここで紹介する内容は執筆時点での情報なのでご注意下さい。

Azure Kubernetes Service (AKS)

Azure Kubernetes Service (以下、AKS)は、フルマネージドなKubernetesクラスタを提供するサービスです。Kunernetes全体を管理するクラスター マスター(俗にいう管理ノード)は、マネージド Azure リソースとして提供され、このノードには課金されず、Kubernetesクラスタをう構成する各ノード(実態はAzure Vrtual Machine (VM)です)のサイズ、ノード数により課金されます。

azure.microsoft.com

Container Registoryの準備

AKSの構築に先立って、Azure Container Registoryに、アプリのコンテナイメージを用意した状態で作業を開始しました。

AKSの構築

今回のターゲットになるアプリは、Azure Database for PostgreSQLと、Azure Redis Cacheを使用します。Azure Database for PostgreSQLは、どのサイズでもVNetに対応していますが、Redis CacheはプレミアムプランでないとVNet対応機能が使えません。プレミアムプランは今回のアプリ規模にはマッチしないということで、VNetへの配置は諦め、スタンダードプランを利用し、IPアドレスで制限する形にすることにしました。

実際の構築はステージング環境を想定して、MSの寺田さんが公開されている下記を雛形にAzure CLIを使って順次構築していきました。

github.com

構成図

今回は、初期開発ということもあり、配置するアプリケーションもシンプルな構成なので、クラスターも極力シンプルな構成を取る方針で下記のような構成をとりました。

f:id:kaz_29:20190127161049j:plain
構成図

デプロイ

admin

adminに関しては、管理者のみが使用する機能なので、シンプルに特定のブランチが更新されると、Azure DevOpsでビルドが始まり自動でリリースされる方式を取りました。

api

apiに関しては、B/Gデプロイメントに近い以下のような形を取りました。

  • 特定のブランチの更新をトリガーにビルド開始
  • UnitTestを実行
  • ビルド番号をタグとして利用し、ACRにコンテナイメージをプッシュ
  • ビルド番号をコンテナのタグ、セレクタとして指定し、green serviceにapply
  • 担当者にリリース承認メールを送信
  • green側で動作確認後に、担当者がリリース
  • blue serviceのセレクタを新しいビルド番号に更新しapply
  • 問題がなければ古いビルド番号のdeploymentを手動で削除

最後の古いデプロイメントの削除に関しては、ビルド番号がエラーなどで飛んでしまうなど自動化するためにはいくつかハードルがあるため、現状は手動で削除する形をとっています。

Azure DevOps側の詳細については後述します。

今回のワークショップでは、モブプログラミングの様なフォーマットで、定期的にドライバを交代しながら作業を進めました、そのおかげで参加した全員が実際の構築を体感できたので、後半では活発にアドバイスすや意見が出て全員の理解度が上がっていることを体感できました。この形式は今後会社の業務でも取り入れていきたいと思います。

最終的にクラスタの構築は、弊社のイケメンエンジニアたちがTerraformで自動構築できるようにしてくれて、いつでもクラスタを再現できるようになりました。AKSクラスタの構築に関する詳細は近いうちにブログにまとめられるはずなので、公開されたら追記します。

Azure DevOps

Azure DevOpsのオーガニゼーションを作成する

まだ、Azure DevOpsのオーガニゼーションがない場合は、https://dev.azure.comにアクセスし作成します。

dev.azure.com

雛形のDevOps Psojectを作成

今回の扱うアプリはPHP(CakePHP)を使っていますが、現状PHPのテンプレートのデプロイターゲットにはAKSが含まれていません。

今回は初のAzure DevOps/AKSの利用ということで、AKSへのデプロイ方法の参考にするために、Java/SpringのテンプレートをつかってAKSへデプロイするプロジェクトを作成し、それをベースにカスタマイズして行く方法をとりました。

まず、雛形となるテンプレートとしてJavaを選択します。

f:id:kaz_29:20190127162442j:plain

フレームワークは何でもいいのでとりあえずSpringを選択します。

f:id:kaz_29:20190127162510j:plain

デプロイ先はAKSを選択します。

f:id:kaz_29:20190127182454j:plain

Azure DevOpsのプロジェクト名、オーガニゼーションを選択し、AKSクラスタはすでに構築済みなので、Use Existingを選択して、デプロイ先のAKSクラスタを指定します。

f:id:kaz_29:20190127162251j:plain

Doneをクリックすると構築が始まるのでしばらく待ちます。いろいろとデプロイが進みAzure Portalに以下のような画面が現れます。

f:id:kaz_29:20190127162100j:plain

これで実際にAzure DevOpsから、自動デプロイできる環境ができますので、内容を確認して各自の環境に合わせてカスタマイズしていくと良いでしょう。 もともと作成されるpipelineを直接直すのではなく、pipelineを新規に作成、サンプルを参考に構築していくのが良いと思います。

DeployのTIPS

秘匿情報の管理

今回のアプリは、JWKトークンを利用するため、ビルド時に証明書のアクセス権などを設定する必要がありますが、証明書をリポジトリに含めることは避けるべきです。当初、KayValutを使用しようと考えていましたが、Azure DevOpsにはSecure filesという機能があることを発見したのでこの機能を利用してビルド時に証明書を取得することにしました。

Pipelines -> Library に Secure filesタブがあるのでここで証明書ファイルをアップロードします。

f:id:kaz_29:20190127184907j:plain

アップロードしたファイルは、ビルドpipeline内で利用できる Download Secure File タスクを使用してダウンロードすることができます。タスクを実行すると、$(Agent.TempDirectory)以下にダウンロードされますので、Command Lineタスクなどを使用して、所定の場所に移動するなどして利用可能です。

f:id:kaz_29:20190127161900j:plain

共通処理をグルーピング

複数のpiplineで共通の処理をtask groupとしてまとめて再利用することができます。

下記のように、まとめたいtaskを選択して、右クリックして Create task group を選択すると作成することができます。

f:id:kaz_29:20190127183055j:plain

Manage task groupを選択すると、Task groupの内容を編集することができます。

f:id:kaz_29:20190127183124j:plain

deplyment.yamlの更新

テンプレートでは、hemlを利用してデプロイを実施しているのですが、今回はビルド時にdeployment.yamlのタグやバージョンを更新してapployする方式をとっています。

下記のようにsedコマンドを使用して、テンプレート内のVERSIONという文字列を、実行中のビルド番号に置換して実現しています。

sed -i -e "s|VERSION|$(Build.BuildId)|g" /home/vsts/work/1/s/kubernetes/deployment.yaml

deployment.yaml抜粋

apiVersion: apps/v1
kind: Deployment
metadata:
  name: appname-VERSION
spec:
  replicas: 3
  selector:
    matchLabels:
      app: appname
...
  template:
    metadata:
      labels:
        app: appname
        version: vVERSION
        stage: staging
    spec:
      securityContext:
        runAsUser: 33
      imagePullSecrets:
        - name: docker-reg-credential
      containers:
      - name: appname
        image: appnameacr.azurecr.io/appname-api:VERSION
....

service.yaml

apiVersion: v1
kind: Service
metadata:
  labels:
    app: appname
  name: green-service
spec:
  ports:
  - port: 80
    name: http
    targetPort: 8080
  selector:
    app: appnemr
    version: vVERSION
  sessionAffinity: None
  type: ClusterIP

metadataの値は文字列である必要があるため、ビルド番号ではなく v10 の様にプレフィックスをつけています。

変更したyamlファイルをrelease pipelineに引き渡す

前の手順でsedで変更したファイルを、release pipelineで利用するために、Artifactディレクトリにコピーする必要があります。

Copy file taskを利用して、必要なファイルを$(build.artifactstagingdirectory) にコピーすることで、後続のpipelineに受け渡すことができます。

f:id:kaz_29:20190127183214j:plain

Build pipeline

で最終的に、build pipelineはこんな形になりました。 Build jobの前に、UnitTest用のJobを実行し、正常に終了したらbuildに進むように設定しています。

証明書ファイルの展開・コピーと、yamlファイルの更新は、Task group化しています。

f:id:kaz_29:20190127162346j:plain

Release pipeline

Release pipelineはこんな感じになりました。

f:id:kaz_29:20190127183314j:plain

Apply to greenApply to blueは以下のように、Post-deployment approvalsを有効にして、担当者の承認後に進むように設定しました。

f:id:kaz_29:20190127183352j:plain

Apply to green では、deploymment.yamlとgreen_service.yamlDeploy to Kubenetes taskを指定してapplyしています。

f:id:kaz_29:20190127183418j:plain

Apply to blueでは、blue_service.yamlをapplyしています。

f:id:kaz_29:20190127183440j:plain

まとめ

ここまでの環境を設定するのは、かなり大変ですし、いろいろとハマったところもあるので、ワークショップで寺田さんにサポートしてもらえなければ挫折していたかもしれません。本当にありがとうございました。

今まで様々な環境を利用してCI/CD環境を構築/運用してきましたが、その中でもAzure DevOpsは、VSTSで蓄積されたノウハウが詰まっていて凄く良いと感じました。もちろんOSSを使ったアプリでも問題なく使えます。このプロジェクトを通して使い倒してノウハウが溜まったらまたいつか紹介できるようにしたいと思います。

また、Kubernetesを利用する場合でも、環境や思想はそれぞれ異なるので、すべての環境が今回の形で対応できるわけではないでですが、構築を検討している方の参考になると嬉しいです。

実際のアプリの開発は、これからが本番なので気合を入れて開発を進めていきたいと思います。

参考URL

Azure DevOpsを使って、PHPコンテナをWeb App for Containersに簡単デプロイ

何度もつぶやいたことがある気がするけど、予定が詰まっているときに限って仕事と関係ないコードを書いてしまい、結局ブログまで書き始めてしまった渡辺です。

2019-03-03追記

記事執筆時点ではDeploy to slotが正常に動作しなかったのですが、Azure App Service Deploy タスクの4.*がリリースされていたのでこちらを使うように変更したところ、正常にデプロイできました!

Azure DevOps

azure.microsoft.com

CI/CDを実現するためのサービスで、以下のような機能が含まれています。

  • Azure Boards - かんばんボード、バックログダッシュボードなど...
  • Azure Pipelines - CI/CDを実行するサービス
  • Azure Repos - Git Repositoryサービス
  • Azure Test Plans - 探索的テスト ツール(?)
  • Azure Artifacts - パッケージ管理ツール(?)

今回は、Azure Test Plans/Azure Artifactsは試していないのでよくわかってないです...。

Azure DevOps Projectの作成

Azure DevOps Projectの作成を開始すると下記のように、ウィザード形式で設定が進みます。

f:id:kaz_29:20190113170315j:plain
Runtimeの選択

  • 次にFrameworkの選択です。Simple PHP/Larabelが選択できますが、今回はSimple PHPを選択します。

f:id:kaz_29:20190113170551j:plain
Frameworkの選択

  • 次にDploy先のサービスを選択します。今回はDocker Containerを使用しますので、Web App for Containersを選択します。

f:id:kaz_29:20190113170727j:plain
Serviceの選択

  • 最後に、プロジェクトの設定をします。

f:id:kaz_29:20190113171043j:plain
プロジェクトの設定

  • Additional settings をクリックすると、Containerや、Container Registryの細かな設定ができます。

f:id:kaz_29:20190113171639j:plain
Additional settings

設定が完了すると、いろいろとデプロイが進みAzure Portalに以下のような画面が現れます。

f:id:kaz_29:20190113172801j:plain
DevOps Projects

画面右上の、エンドポイントURLをクリックするとdeployされたサイトを見ることができます。

f:id:kaz_29:20190113173041j:plain
作成直後のWebサイト

ここまでで、10数分くらいですかね。これで、リポジトリからPipeline経由でデプロイされる環境ができちゃいます。簡単ですね。

Simple PHPの中身を覗いてみる

sshでcloneするには、Azure Reposの右上にある Clone をクリックすると出てくる画面で、Manage SSH keys に遷移すると公開鍵を登録することができます。

f:id:kaz_29:20190113181341j:plain
Azure Repos

Simple PHPをdeployすると初期状態では、リポジトリには下記のようなシンプルなPHPアプリが生成されています。

$ tree -L 2 .
.
├── Application
│   ├── Dockerfile
│   ├── LICENSE
│   ├── Readme.md
│   ├── composer.json
│   ├── css
│   ├── fonts
│   ├── img
│   └── index.php
└── ArmTemplates
 ├── container-webapp-template.json
 └── containerRegistry-template.json

ちょっと中身を覗いてみましょう。

Application/composer.json

application-insightsのパッケージだけが含まれている、シンプルなアプリ

{
    "require": {
        "microsoft/application-insights": "*"
    }
}

Application/Dockerfile

PHPコミュニティ公式の 7.0.6-apache をベースにしているようです。 いくつかのaptパッケージ、php拡張、composerコマンドをインストールして、composer installしています。

FROM php:7.0.6-apache
LABEL maintainer="Azure App Service Container Images <appsvc-images@microsoft.com>"
RUN apt-get update -y && apt-get install -y openssl zip unzip git
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
RUN docker-php-ext-install pdo mbstring

COPY . /var/www/html/
RUN composer install

Application/index.php

autoloadして、application-insightsにリクエストログ投げているだけのほぼ静的なページです。

<?php
    require_once 'vendor/autoload.php';
    $app_insights_instrumentation = getenv('APPINSIGHTS_INSTRUMENTATIONKEY');
    $telemetryClient = new \ApplicationInsights\Telemetry_Client();
    $telemetryClient->getContext()->setInstrumentationKey($app_insights_instrumentation);
    $telemetryClient->trackRequest('Server Requests','Azure DevOps Project', time());
    $telemetryClient->flush();
?>

<!DOCTYPE html>
<html lang="en">

...

ArmTemplates/

ArmTemplates以下には、Container RegistryとWeb Appのプロビジョニングに使用するarm templateが保存されています。

要は、80でWebサーバさえ上がっていればOKそうです。~/Application/ って配置がちょっと気になりますが...。

Azure DevOps内の流れ

初期状態では、Pipeline内にはBuilds/Releasesの2つにpipelineが設定されています。

Build

Buildには以下のようなpipelineが設定されています。

  • materブランチの更新をトリガーに実行される
  • Container Registoryを作成する
  • コンテナをbuild
  • コンテナをContainer Registryにpush

f:id:kaz_29:20190113181533j:plain
Pipelines/Build

Releases

Releasesには以下のようなpipelineが設定されています。

  • Builds完了をトリガーに実行される
  • Web App for Containerを作成する
  • コンテナをdeployする

f:id:kaz_29:20190113181619j:plain
Pipelines/Releases

デプロイのオプションには、slotにデプロイさせる設定などがありますね。

まとめ

こんな感じで、単純なWebアプリをデプロイするpipelineがすごく簡単に構築できます。 他のサービスの構築やPipeline内でテスト、静的解析を実行したり、B/G deploymentをするなど、普段別サービスを使ってやっていることを置き換えていろいろ試してみようと思います。

おまけ

slotを使って、B/G deploymentをしようとしたのですが、staging slotにdeployする設定にしたのにproduction slotにもデプロイされてしまう謎挙動が発生して解決できていません (;_; 2019-03-03修正

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のランタイムだけのようですが、追々他のランタイムも追加されるでしょう。

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