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だと厳しいかもしれません。

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

AlamofireのRequest Paramater エンコードをカスタマイズする

最近、PHP(CakePHP2,CakePHP3,Lumen)、iPhoneアプリ(Objective-C,Swift)とAndroidアプリの運用をしていて頭ん中がカオスになっているわたなべです。

とある趣味アプリで、Alamofireを使って某APIを使おうとしてハマったのでメモっときます。

AlamofireのRequest Paramater エンコード

基本的には、RFC3986に従ってエンコードされるのですが、'/' と '?' がエンコードされません。実際のエンコード処理にはこんなコメントがあります。

// does not include "?" or "/" due to RFC 3986 - Section 3.4

RFC 3986 - Section 3.4ってなんだよ?ってことで、RFC3986 日本語訳を見るとこんな風に書いてあります。

スラッシュ ("/") と疑問符 ("?") の文字は、query 要素の中のデータを表すかもしれない。 いくつかの古い、エラーのある実装では、それが相対的参照 (Section 5.1) の基底 URI として使用される場合、階層的な区切りを探す時に query データと path データの区別に失敗する事が多いので、そのようなデータを正しく扱わないかもしれない事に注意せよ。 しかし、query 構成要素はしばしば "key=value" の対の形式で識別するための情報を運ぶために使用され、そこで頻繁に使用された値は別の URI の参照なので、時にはそれらの文字をパーセントエンコーディングする事を避けるほうがユーザビリティのためにはよい。

https://triple-underscore.github.io/RFC3986-ja.html#section-3.4

時にはそれらの文字をパーセントエンコーディングする事を避けるほうがユーザビリティのためにはよい。」これを根拠に'/' と '?'がエンコードされないようです。。。

普通に使用している分には気づかなかったのですが、APIを使うためにSignetureを生成する場合に、ハッシュ化する元の文字列が変わってしまうため問題が起こります。

ParameterEncodingを独自実装

AlamofireのParameterEncodingには、一般的に使用するURLエンコード以外にも以下のエンコードが定義されています。

  • URL
  • URLEncodedInURL
  • JSON
  • PropertyList
  • Custom

今回はCustomを使用して、'/' と '?'もエンコードするようにカスタマイズします。

Alamofireのencode処理を参考に作ったのがこんなの。

gist.github.com

この関数で作成したエンコーディング処理を以下のように指定することで独自にエンコードすることができます。

gist.github.com

わかってしまえば、たいしたことではないのですが、なかなか原因がわからずちょっと手こずりました(;_;

Ansible2.1がARMに対応したので"少しだけ"試してみた #2

先日、Ansible2.1でARMを試した記事を書きましたが、その後もう少し深くいじってみました。

kaz29.hatenablog.com

前回はリソースグループを作っただけでしたが、今回は、牛尾さん(id:simplearchitect)の以下の記事でTerraformを使ってやっていることをなぞってみました。

qiita.com

設定内容

設定した内容はこんな感じ。yamlでかけるのは読みやすくていいですね。

---

azure_resource_group:
  - name: Testing
    region: japanwest
    state: present
    tags:
      testing: testing
      delete: never

azure_virtual_network:
  - name: test
    state: present
    resource_group: Testing
    address_prefixes_cidr:
        - "10.1.0.0/16"
    dns_servers: []
    tags:
        testing: testing
        delete: on-exit

azure_subnet:
  - name: acctsub
    state: present
    virtual_network_name: test
    resource_group: Testing
    address_prefix_cidr: "10.1.2.0/24"

azure_publicipaddress:
  - name: ansibletestip
    state: present
    resource_group: Testing
    allocation_method: Static
    domain_name: ansibletestlinux
    tags:
        testing: testing
        environment: Production

azure_networkinterface:
  - name: testnic1
    state: present
    resource_group: Testing
    virtual_network_name: test
    subnet_name: acctsub
    security_group_name: 
    public_ip_address_name: ansibletestip
    private_ip_allocation_method: Static
    private_ip_address: 10.1.2.10
    tags:
        testing: testing

azure_storage_account:
  - name: accsa1971eey
    state: present
    resource_group: Testing
    type: Standard_LRS
    tags:
        testing: testing

azure_storage_container:
  - name: vhds
    state: present
    resource_group: Testing
    storage_account_name: accsa1971eey
    tags:
      testing: testing

azure_virtualmachine:
  - name: AnsibleVM02
    state: present
    resource_group: Testing
    vm_size: Standard_A0
    storage_account: accsa1971eey
    storage_container_name: vhds
    network_interfaces: testnic1
    admin_username: kaz
    remove_on_absent:
      - virtual_storage
    image:
      offer: UbuntuServer
      publisher: Canonical
      sku: '14.04.2-LTS'
      version: latest
    tags:
      testing: testing

Ansibleを使用して実行してる内容は以下。

  1. ストレージクループの作成
  2. 仮想ネットワークの作成
  3. サブネットの作成
  4. パブリックIPアドレスの作成
  5. ネットワークI/Fを作成
  6. ストレージアカウントの作成
  7. ストレージコンテナの作成
  8. VMの作成

今回作ったものはGithubにあげましたので参考になれば。

github.com

ポータルを使わないで、いつものAnsibleで構築して何回でもやり直せるのはかなり便利ですね。

まとめ

まだ理解しきてれていないので、VMを削除しようとしたらエラーが出てうまく削除できませんでした。VM削除時に自動で消されるものがあるようなので、その辺りが原因(`remove_on_absent`)かなと思うので、もう少し調べようと思います。

とはいえ、ポータルをポチポチしないで良いのはとても便利なので今後 Terraformとどっちを使うかも含めて色々検討しようと思います。