kakakakakku blog

Weekly Tech Blog: Keep on Learning!

CircleCI で ECS にデプロイをするサンプルプロジェクト circleci/go-ecs-ecr を試した

CircleCI を使って,ECS に Docker コンテナをデプロイする方法を調べていたら,CircleCI が公開しているサンプルプロジェクト circleci/go-ecs-ecr の存在を知ったので,さっそく試してみた.

circleci/go-ecs-ecr を動かすと,Golang の API が動く Docker イメージを作成して,ECR にプッシュして,ECS にデプロイする,という流れを簡単に試すことができる.僕自身が ECS に入門したばかりなので,勉強のキッカケとしてもお手軽でとても良かった.

github.com

ザックリと構成図を書くと,こんな感じになる.

f:id:kakku22:20170419014555j:plain

circleci/go-ecs-ecr を Fork する

まず最初に circleci/go-ecs-ecr を Fork する.このままだと circle.ymldeploy.sh に定義されたリージョンが全てバージニア (us-east-1) になっているため,東京 (ap-northeast-1) に変更する必要がある.例えば以下のように.

github.com

ECR にリポジトリを作成する

ECR に go-sample-webapp という名前のリポジトリを作成しておく.また aws ecr get-login コマンドで,ECR にログインできることを確認しておく.

$ $(aws ecr get-login --region ap-northeast-1)

ECS で仮のタスク定義を作成する

CircleCI で実行するデプロイで自動的に ECS のタスク定義を作成するため,ここでは ECS クラスタを起動するためだけに仮のタスク定義を作成する.名前は sample-webapp-task にしておく.

ECS クラスタと ECS サービスを作成する

次に ECS クラスタと ECS サービスを作成する.名前は以下とする.

  • クラスタ名 : sample-webapp-cluster
  • サービス名 : sample-webapp-service
  • タスク定義名 : sample-webapp-task-family(自動的にデプロイされる)
    • タスク定義名のことをファミリーと呼ぶ

今回は検証用途なので,コンテナインスタンスは t2.micro を1台にした.ELB / ALB も使わないため,パブリックサブネットに配置して,限られた IP アドレスから 80 ポートを許可するようにした.

また Desired Count も 1 にし,Minimum Healthy PercentMaximum Percent はデフォルトのままにした.日本語コンソールの「最小ヘルス率」と「最大率」という訳が微妙すぎるでしょ…!

CircleCI の設定をする

Fork した go-ecs-ecr リポジトリで CircleCI を動くように設定したら,まずは CircleCI 専用の IAM User を作成する.名前は circleci-ecs とした.ポリシーは以下をアタッチする.

  • AmazonEC2ContainerRegistryFullAccess
  • AmazonEC2ContainerServiceFullAccess

次に CircleCI の Environment Variables で,以下の環境変数を設定しておく.

  • AWS_ACCOUNT_ID
  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY

ビルドを実行する

あとは CircleCI でビルドを実行すると ECS に Docker コンテナがデプロイできる.デプロイ後にコンテナインスタンスにアクセスすると,ちゃんとレスポンスが返ってきた.

$ curl http://xxx.xxx.xxx.xxx/
Hello World!%

仕組みを学ぶ

circle.yml

  • docker build を実行して $CIRCLE_SHA1 をタグとして Docker イメージを作成する
  • テスト時には docker run でコンテナを実行して,接続を確認する
  • master ブランチにプッシュされた場合は deploy.sh を実行する

deploy.sh

  • AWS CLI の設定をする
  • ECR に Docker イメージをプッシュする
  • タスク定義のリビジョンを更新して,最新の Docker イメージを反映する
  • サービスに紐付くタスク定義を更新して ECS にデプロイをする

詳しくは CircleCI のドキュメントにも載っているので,合わせて見ると良いと思う.

circleci.com

学んだこと

ECS は設定項目が多く,ポチポチとデフォルト設定のまま進めていたら,少し気になる点を発見した.

まず,クラスタを新規に作成するときに,ネットワーキングの部分で,デフォルトでは VPC を新規に作成する設定になっていた.何となく CloudFormation のスタック変更が長いなぁと思っていたら VPC まで作られていて,既存の VPC で動かすことが多いだろうし,ここは注意しておきたいところだなと思った.あと,自動的に作られるセキュリティグループは,デフォルトで 80 ポートを 0.0.0.0 で開放していた.ここも注意しておきたいと思った.

まとめ

ECS を学ぶのに最高な Black Belt 資料

非常によくまとまっていて素晴らしい資料だと思う.今のところ毎日1回は見ている気がする.また今月に資料の更新もされていて,情報の鮮度が保たれているという点も助かる!

www.slideshare.net

計画する技術

今日は社内勉強会で「計画する技術」というタイトルで発表をした.

前から少し「計画」のところに課題感があって,そのあたりの知識を組織に広めて欲しいというオーダーもあったため,僕が日々考えていることを言語化して,発表することにした.僕は今までに様々な「計画」の経験があり,実際に今の組織でも難易度の高いプロジェクトを何度も計画し,遂行してきたため,「計画」に対する信頼残高は増えているのではないかと思う.

発表資料

意識したこと

今回,発表資料を作るときに意識したことが2個あった.他にも話したいことはたくさんあったけど,組織マネジメントの話はまた別でしようと思って,あくまで「計画」に特化した話にした.

  • 明日からすぐに使える話をする
  • 開発プロセスに依存しない話をする

1. 明日からすぐに使える話をする

原理原則すぎる話や,難しい法則の話は控えるようにした.そういう話をしてしまうと,その場では「おー,そういう法則があるのか」と,わかった気になるけど,いざ自分で咀嚼しようとすると「で,どうしたら良いんだろう?」となってしまう可能性が高いと思ったから.例えば「不確実性コーン」の話とか,「バッファ計算」の話とか,「PMBOK」の話とか.

2. 開発プロセスに依存しない話をする

今の組織の開発プロセスは,ウォーターフォールでもないし,スクラムでもないし,カンバンでもなく,また何かしらの「型を崩した」ものでもなく,独特なものになっていると思う(個人的には悪い面もあるとは思う).そのときに「スクラムではこれは必須だから」という話をしても,他人事のように聞こえてしまう可能性が高いと思って,特定の開発プロセスに依存しない話をした.あくまでこれは即効性を意識したためで,抜本的に開発プロセスを見直す機会があるのであれば,ゼロから立て直したいと思う.

よく読む本

前に「カンバン勉強会」を実施したときの資料

kakakakakku.hatenablog.com

参考になる記事

PHP の Elasticsearch クライアント Elastica で Amazon ES に接続する

PHP から Elasticsearch を操作するために Elastica というライブラリを使っている.今まで EC2 で運用していた Elasticsearch を Amazon ES に移行する話があり,Elastica から Amazon ES を操作するときに悩むことが多かったため,まとめておこうと思う.

github.com

ちなみに,僕は使ったことはないけれど,Elastic 社から公式に提供されている elasticsearch-php というライブラリもある.ただ,Elastic 社の公式ライブラリだと,積極的に Amazon ES 対応をすることはないだろうし,Amazon ES を使う場合は,サードパーティライブラリの Elastica の方が良い気がする.

github.com

Amazon ES に接続する方法

Amazon ES に接続する方法を復習しておくと,大きく3種類ある.

  • リソースベース
  • IP ベース
  • IAM ユーザーとロールベース

docs.aws.amazon.com

今回のように,アプリケーションから Amazon ES に接続する場合は IAM を使う.IAM Role を使うのがベスト.必要に応じて IP 設定を変更して運用することも考えられるが,Amazon ES で IP 設定を変更すると,ノードが全台作り直されてしまうため,クラスタのデータ規模によっては1時間程度待たされることもあるので,実質不可能だと思うし,そもそも,アーキテクチャ的に微妙すぎる.

また,IP ベースの場合は未署名の匿名リクエストも受け付けることができるが,IAM を使う場合,署名付きリクエスト(AWS 署名バージョン 4)を送る必要がある.よって,ライブラリが署名付きリクエストに対応している必要がある.

docs.aws.amazon.com

docs.aws.amazon.com

Elastica から Amazon ES に接続する

github.com

Issue を見ると,Elastica は既に署名付きリクエストの対応がされているが,実装例などのドキュメントが全くなくて,よくわからなかった.困ったので Gitter で聞いてみたら,すぐ教えてもらうことができて助かった.簡単過ぎるけど,今回の構成をまとめた.

f:id:kakku22:20170407002340j:plain

ライブラリをインストールする

署名付きリクエストを送るためには,Elastica 3.1.1 以上が必要になる.ただし,このままで実行すると,以下のエラーが出る.

Class 'GuzzleHttp\Psr7\Uri' not found

Elastica の composer.json を見ると,suggest プロパティに以下の設定がある.suggest とは,基本的には必要ないけど,入れておくと便利な依存パッケージのことを意味している.Ruby の Gemfile にはそういう概念がないため,今まで知らなかった.

{
    (中略)
    "require-dev": {
        "aws/aws-sdk-php": "~3.0",
        "guzzlehttp/guzzle": "~6.0"
    },
    "suggest": {
        "aws/aws-sdk-php": "Allow using IAM authentication with Amazon ElasticSearch Service",
        "egeloen/http-adapter": "Allow using httpadapter transport",
        "guzzlehttp/guzzle": "Allow using guzzle 6 as the http transport",
        "monolog/monolog": "Logging request"
    },
    (中略)    
}

よって,Amazon ES に IAM で接続する場合は,以下のような composer.json になる(バージョン設定はご自由に).

{
    (中略)
    "require": {
        "ruflin/Elastica": "~3.2",
        "aws/aws-sdk-php": "~3.25",
        "guzzlehttp/guzzle": "~6.2"
    },
    (中略)
}

まだ aws/aws-sdk-php v2 を使っていると動かないため,合わせて aws/aws-sdk-php をバージョンアップする必要がある.

Elastica のクライアント実装

以下のように transportAwsAuthV4 を設定して,aws_region を設定すると,Amazon ES に IAM で接続できるようになる.

$client = new Elastica\Client(
    array(
        'host'       => 'xxx.ap-northeast-1.es.amazonaws.com',
        'port'       => 80,
        'transport'  => ['type' => 'AwsAuthV4', 'postWithRequestBody' => true],
        'aws_region' => 'ap-northeast-1'
    )
);

もし IAM Role ではなく,クレデンシャルを埋め込む場合(非推奨な実装)は,以下のようになる.

$client = new Elastica\Client(
    array(
        'host'                  => 'xxx.ap-northeast-1.es.amazonaws.com',
        'port'                  => 80,
        'transport'             => ['type' => 'AwsAuthV4', 'postWithRequestBody' => true],
        'aws_access_key_id'     => 'yyy',
        'aws_secret_access_key' => 'xxx',
        'aws_region'            => 'ap-northeast-1'
    )
);

まとめ

  • Elastica は Amazon ES の接続をサポートしている
  • ドキュメントが詳しくないため,困ったら Gitter で聞いてみると良い
  • 必ず IAM Role を使おう

参考情報 : Amazon ES を運用するときに知っておくと便利なこと

kakakakakku.hatenablog.com

mysqldiff を使って継続的に MySQL のデータベーススキーマの差分をチェックする

最近,環境ごとのデータベーススキーマの差分をチェックする機会があった.プロダクション環境とステージング環境ならまだしも,開発環境だと検証のために追加したインデックスがそのままになっていたり,開発が途中で止まってしまって日の目を見ることがなかったテーブルが残っていたり,そういうことって比較的あるのではないかなと思う.特に今の環境だと,マイグレーションの仕組みが整っていないという課題もあり,より一層,データベーススキーマに差分が出やすくなってしまっている.

今回は MySQL から公式に提供されている mysqldiff というツールを使ってデータベーススキーマの差分をチェックした.

mysqldiff をインストールする

mysqldiff は MySQL Utilities という MySQL の管理ツールパッケージの中に同梱されている.現在だと v1.6 が最新になっている.

yum で簡単にインストールすることができる(リポジトリは適宜指定すること).

$ sudo yum install -y mysql-utilities

$ which mysqldiff
/usr/bin/mysqldiff

$ mysqldiff --version
MySQL Utilities mysqldiff version 1.6.5
License type: GPLv2

mysqldiff を使う

基本的には以下のように使う.データベースだけではなく,テーブルを指定して限定的な差分をチェックすることもできる.

  • --server1 … 比較するデータベースの接続設定
  • --server2 … 比較するデータベースの接続設定
  • --force … デフォルトだと,差分が1件出ると止まってしまうため,最後まで流すために指定する
  • --skip-table-options … テーブルのオプションに差分があっても無視するために指定する
  • --changes-for=server2 … デフォルトだと server1 を修正する形で認識されるため,server1 を起点として server2 を修正する形にする
$ mysqldiff --server1=root@localhost --server2=root@localhost database_a:database_b --force --skip-table-options
$ mysqldiff --server1=root@localhost --server2=root@localhost database_a.users:database_b.users --force --skip-table-options

サンプルとして users テーブルの差分をチェックしてみた.以下の例では email_UNIQUE 制約が存在しないことをチェックできている.

$ mysqldiff --server1=root@localhost --server2=root@localhost database_a.users:database_b.users --force --skip-table-options --changes-for=server2
# server1 on localhost: ... connected.
# server2 on localhost: ... connected.
# Comparing database_a.users to database_b.users           [FAIL]
# Transformation for --changes-for=server2:
#

--- database_a.users
+++ database_b.users
(中略)
   `email` varchar(255) DEFAULT NULL,
(中略)
   `updated_at` int(10) unsigned DEFAULT NULL,
   `deleted_at` int(10) unsigned DEFAULT NULL,
   PRIMARY KEY (`id`),
-  UNIQUE KEY `email_UNIQUE` (`email`),
# Compare failed. One or more differences found.

完全に一致している場合は,以下のように [PASS] と表示される.

$ mysqldiff --server1=root@localhost --server2=root@localhost database_a.users:database_b.users --force --skip-table-options --changes-for=server2
# server1 on localhost: ... connected.
# server2 on localhost: ... connected.
# Comparing database_a.users to database_b.users    [PASS]
# Success. All objects are the same.

差分を SQL 形式で出力する

--difftype=sql を指定すると,データベーススキーマを一致させるために必要な SQL を出力することができる.

$ mysqldiff --server1=root@localhost --server2=root@localhost database_a.users:database_b.users --force --skip-table-options --changes-for=server2 --difftype=sql
# server1 on localhost: ... connected.
# server2 on localhost: ... connected.
# Comparing database_a.users to database_b.users           [FAIL]
# Transformation for --changes-for=server2:
#

ALTER TABLE `database_b`.`users`
  ADD UNIQUE INDEX email_UNIQUE (email);

# Compare failed. One or more differences found.

微妙なところ : AUTO_INCREMENT だけを無視することができない

比較対象のデータベースが TRUNCATE されてなく,AUTO_INCREMENT を保持している場合,デフォルトだと差分として検知されてしまう.そのために --skip-table-options を使っているが,これだと AUTO_INCREMENT だけでなく ENGINECHARSET の差分も無視してしまう.ここは少し微妙だなと思った.

--- database_a.users
+++ database_b.users
(中略)
-) ENGINE=InnoDB AUTO_INCREMENT=123 DEFAULT CHARSET=utf8
+) ENGINE=InnoDB DEFAULT CHARSET=utf8
# Compare failed. One or more differences found.

微妙なところ : --difftype=sql は完璧じゃない

今回試したケースだと,MySQL の PARTITION の定義が抜けている場合に,データベーススキーマの差分としては検知できるものの,--difftype=sql では検知されなかった.現状だと --difftype=sql は完璧じゃなく,参考程度に使うのが良いと思う.

CI として継続的に差分をチェックする

日々開発を進めている中で,データベーススキーマに差分が生まれてしまうことはあると思う.よって今回は mysqldiff を使ったデータベーススキーマの差分チェックを CI として継続的に実行できるように自動化した.よって,意図しない差分が出た場合は,容易に気付くことができるようになった.

補足 : mysqldump --no-data を使う

mysqldiff の紹介をメインに書いたため省略してしまったけど,実際に使う場合は環境ごとにネットワークが分離されていることも多いと思う.まさに今回もそのような環境下で試していたものの,mysqldump --no-data でデータベーススキーマを取得して,S3 にアップロードすることによって,CI を実行するサーバに各環境のデータベーススキーマを集約することができる.補足として書いておく.

まとめ

  • mysqldiff を使うと,MySQL のデータベーススキーマの差分をチェックすることができる
  • オプションが多くあるため,ドキュメントを確認すること
  • AUTO_INCREMENT の差分が出る場合は --skip-table-options オプションを使うが,CHARSET などの差分も無視されてしまう
  • --difftype=sql は完璧じゃないので,参考程度に使う
  • 一回実行して終えるのではなく,CI として継続的に差分をチェックするべき

Redash を Backup & Restore する

サービスメトリクスを可視化するために Redash を導入しようと考えていて,可視化の部分だけじゃなく,運用面の調査もしていた.

まず Redash をどうやってバックアップするんだろう?と思って調べてみたら,公式ドキュメントに手順がまとまっていた.一言で言うと,Redash の内部で使っている PostgreSQL のバックアップを取得しておけば良いとのことだった.ただし,公式ドキュメントの手順だとバックアップを保管する部分の記載がなかったため,今回は S3 で保管できるように追加で対応をした.

Re:dash → Redash

余談で,正式名称は Re:dash → Redash に変更されているんだけど,案外知らない人が多いように思う.覚えておきましょ!

github.com

構成

シンプルに以下の構成にした.AWS の場合は AMI 経由でインスタンスを立ててしまうのが最も簡単だと思う.

f:id:kakku22:20170327230844j:plain

1. Redash Backup

まず,redash データベースのサイズを確認する.PostgreSQL の pg_database テーブルから取得できる.以下は Redash を起動して少し設定をした状態で確認した.

postgres=# select t1.datname AS db_name, pg_size_pretty(pg_database_size(t1.datname)) as db_size
postgres-# from pg_database t1
postgres-# where t1.datname = 'redash';
 db_name | db_size
---------+---------
 redash  | 7794 kB
(1 row)

次に pg_dump でバックアップを取得する.

$ sudo -u redash pg_dump redash | gzip > redash_backup.gz

簡単!

2. S3 Upload

日次バックアップを取得して S3 に保管するために,少し追加で準備をする.

まず Redash インスタンスに AWS CLI をインストールする.Amazon Linux ではなく Ubuntu だからインストールする必要がある.

$ sudo pip install --upgrade awscli

次に S3 バケットを作成して,IAM Role を EC2 にアタッチする.Action はもっと絞っても良いと思う.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1234567890123",
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": [
                "arn:aws:s3:::xxx",
                "arn:aws:s3:::xxx/*"
            ]
        }
    ]
}

先月の新機能リリースで,既存の EC2 に IAM Role をアタッチできるようになったのは最高に便利だと思う.既に何度も助けられてる!

次に以下のようなスクリプトを用意して,crontab でスケジュール実行をすれば S3 に定期的に保管できる.ハウスキーピングなどは別途考える必要があると思う.

#!/bin/sh

sudo pip install --upgrade awscli

sudo -u redash pg_dump redash | gzip > redash_backup_$(date +%Y%m%d_%H%M%S).gz

backup="$(ls -1t | grep redash_backup | head -n 1)"

S3_BUCKET_NAME='xxx'

aws s3 cp ${backup} s3://${S3_BUCKET_NAME}/

3. Redash Restore

公式ドキュメントの手順通りにコマンドを実行したら正常にリストアすることができた.バックアップファイル名とパスワードは適宜変更してもらえればと!

$ sudo -u postgres -i

$ dropdb redash
$ createdb -T template0 redash
$ gunzip -c redash_backup_xxx_yyy.gz | psql redash

$ psql -c "ALTER ROLE redash_reader WITH PASSWORD 'xxx';"

$ psql -c "grant select(id,name,type) ON data_sources to redash_reader;" redash
$ psql -c "grant select(id,name) ON users to redash_reader;" redash
$ psql -c "grant select on events, queries, dashboards, widgets, visualizations, query_results to redash_reader;" redash

まとめ

  • 正式名称は Re:dash ではなく Redash に変更されている
  • Redash を運用するときは定期的にバックアップを取得しよう
  • リストアも一度は確認しておこう