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とどっちを使うかも含めて色々検討しようと思います。

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

Ansible2.1が発表された記事がFacebookに流れてきたので、何気に眺めていたらARM(Azure Resource Manager)のサポートが追加されたらしいので少し試してみた。

Broader support for Microsoft Azure, expanding Ansible’s support for hybrid cloud deployments, including the ability to take advantage of Azure’s Resource Manager functionality.

www.redhat.com

お試しの前に...

実務でAnsibleを使っている場合(特に1.x系)、影響が出てしまうと困るので以下などを参考にpyenv/pyenv-virtualenvを入れて環境を切り替えられるようにしてから試すほうがいいでしょう。

qiita.com

Azure Python SDKのインストール

pyenv環境にazure SDKを入れて試したところ以下のようなエラーが出て、SDKを認識できませんでした。

fatal: [ansible.decr.jp]: FAILED! => {"changed": false, "failed": true, "msg": "The Azure Python SDK is not installed (try 'pip install azure') - No module named enum"}

試しに global の方に入れてみたところ認識されました。*1

$ sudo pip install azure==2.0.0rc2

ちなみに、最新のSDKは2.0.0rc4だったのでこちらで試したところ以下のようなエラーで動作しませんでした。

fatal: [ansible.decr.jp]: FAILED! => {"changed": false, "failed": true, "msg": "Expecting azure.mgmt.compute.__version__ to be >= 2016-03-30. Found version Do you have Azure >= 2.0.0rc2 installed?"}

この辺りはまだ出たばかりなので、今後の改善を期待したいですね。

Ansibleのインストール

Ansible本体をインストールします

$ pip install ansible

サービスプリンシパル認証のための設定

以下の2のあたりを参考にclient_id/secret/tenant_idなどを取得する

qiita.com

Ansibleの設定を書く

  • hosts/hosts

Asure関連の処理はlocalで動作するのでhostsはこんな感じで適当に...

[ansibletest]
ansibletest.example.com
  • roles/azure/tasks/main.yml

<サブスクリプションID>などの設定は各自の環境に合わせて修正してください。*2

- name: Create a resource group
  azure_rm_resourcegroup:
    name: Testing
    location: japanwest
    state: present
    subscription_id: <サブスクリプションID>
    client_id: <クライアントID>
    secret: <secret>
    tenant: <テナントID>
    tags:
      testing: testing
      delete: never
  • azuretest.yml
---
- name: ansible test
  hosts: ansibletest
  connection: local

  roles:
    - azure

playbookを実行する

$ ansible-playbook -i hosts/hosts default.yml

PLAY [ansible test] ************************************************************

TASK [setup] *******************************************************************
ok: [ansibletest.example.com]

TASK [azure : include] *********************************************************
included: /Users/kaz/dev/ansible21/roles/azure/tasks/resource-group.yml for ansibletest.example.com

TASK [azure : Create a resource group] *****************************************
ok: [ansibletest.example.com]

PLAY RECAP *********************************************************************
ansibletest.example.com : ok=3 changed=0 unreachable=0 failed=0


問題なく実行されると、portal上でもリソースグループが追加されました。


f:id:kaz_29:20160531065723j:plain

まとめ

今回はリソースグループを作ってみただけですが、業務に適用できるか引き続き色々試してみようと思います。
TerraformもARM対応したりと色々面白くなってきましたね(^^。

*1:python力足らないんで原因わからないですが、引き続き調査。

*2:ドキュメントには、subscription_idなどの情報を環境変数や、~/.azure/credentialsに設定できると書いてあるのですがうまくいかなかったのでこの辺りも引き続き調査。