2017年4月11日火曜日

Docker Cloud ボタンを試してみた

概要

Docker Cloud ボタンはリポジトリで管理されている docker-cloud.yml の定義を元に Docker cloud に簡単にデプロイできちゃうボタンです
Heroku ボタンのようなものです
今回は Github のプロジェクトに Docker Cloud ボタンを作成してボタンを押してデプロイしてからアプリが動作するところまでやってみました

環境

  • CentOS 7.3
  • docker 17.03
  • Docker Cloud (2017/04/06)

docker-cloud.yml の作成

リポジトリのルートディレクトリに作成する必要があります
Heroku でいうところの app.json になります
docker-cloud.yml の他には docker-compose.yml, fig.yml でも代用できます

またポイントしては build ディレクティブが使えません
例えば以下のような docker-cloud.yml があった場合 Docker Cloud 上にデプロイすることはできません

web:
  build: .
  ports:
    - 80:9292
  links:
    - redis
  environment:
    - REDIS_URL=redis://redis:6379
redis:
  image: redis:latest

この場合以下のように build をやめて image ディレクティブを使用します
なので、この場合、ビルドした web イメージは事前に Dockerhub で公開しておく必要があります

web:
  image: your-github-name/app
  ports:
    - 80:9292
  links:
    - redis
  environment:
    - REDIS_URL=redis://redis:6379
redis:
  image: redis:latest

作成できたらリポジトリに push しましょう

Docker Cloud ボタンの作成

次に Docker cloud ボタンを作成します
Github などであれば README.md に以下を追加すれば OK です

[![Deploy to Docker Cloud](https://files.cloud.docker.com/images/deploy-to-dockercloud.svg)](https://cloud.docker.com/stack/deploy/?repo=https://github.com/your-github-name/app)

単純に Docker cloud に対するリンクを作成しているだけです
ボタンを作成する前にテストしたいということであれば

  • https://cloud.docker.com/stack/deploy/?repo=https://github.com/your-github-name/app

に単純にブラウザでアクセスするだけで OK です
もちろんその際には docker-cloud.yml の配置が必要です

ボタンを押してみる

ではボタンが作成できたら押してみましょう
ボタンを押すと Docker cloud の画面に遷移すると思います
docker_cloud_button1.png

これで「Create & Deploy」してみましょう
エラーになる場合は yml ファイルのフォーマットがおかしい他、イメージが Dockerhub になかったり、build ディレクティブを使っていないか確認しましょう

また、ここでボタンを押す時にコンテナを動かすノードが存在していないとエラーになります
例として AWS の EC2 ノードを Docker Cloud のノードとして動作させる方法はこちらを御覧ください

ノードもちゃんとある状態でボタンを押すのに成功すると以下の Stacks の画面に移動すると思います
どうやら Docker cloud ボタンからデプロイした場合は Stack として作成できるようです
docker_cloud_button2.png

Stack の状態が RUNNING になればノード上へのデプロイは成功です

動作確認

Docker cloud がデフォルトで用意しているエンドポイントにアクセスしてみましょう
エンドポイントの情報は上記の Stacks の画面のすぐ下にあります
ちゃんとページが表示されることが確認できると思います

最後に

Docker Cloud ボタンを試してみました
ファイルを配置したりコンテナを動作させるノードがなかったりしなきゃいけないと結構つまづきポイントもありました

Heroku ボタンに慣れていればほぼそれと同じ感じなので簡単にできるかなと思います
あとは Docker Cloud で動作させることが、どれだけ需要があるかは、まぁ何とも言えません、、、

ちょっと気になったのはクラスタが複数ある場合にどちらのクラスタへのデプロイが優先されるかです
今回は 1 クラスタだけだったので問答無用でそのクラスタのノードが選択されましたが、クラスタが複数ある場合もいい感じで分散してくれるのかが気になりました
URL ではクラスタやノードを特に指定していないので、その辺は Docker cloud 側におまかせだと思うのですが、その辺のアルゴリズムが気になりました
単純に上からとかもあるかもしれません

参考サイト

2017年4月10日月曜日

ECS と ALB を組み合わせてコンテナを簡単にスケールアウトする

概要

ECS と ALB を連携してコンテナアプリケーションのスケールアウトを行ってみました
ECS にはホスト側のポートを動的に決定する仕組みがあり、その動的に割り振れたポートを ALB に接続することでスケールアウトを実現します

環境

  • CentOS 7.3
    • docker 17.03
    • ruby 2.3.3
    • gem 2.6.11
    • aws cli 1.11.71
  • ECS, ALB (2017/04/05 時点)

ECS の作成

前回 チュートリアルから基本的なコンポーネントを作成しました
今回もその方法で作成していきます
基本的な流れは同じなのでポイントだけ紹介します

「Amazon ECS クラスターにサンプルアプリケーションをデプロイする」「Amazon ECR によりコンテナイメージをセキュアに保存する 」には両方チェックして次に進みます

レジストリですが今回はアプリケーションをスケールさせたときにどのコンテナにリクエストが行っているかちゃんと確認するために独自のコンテナアプリを作成します
ecs_with_alb1.png

ついでに作成したレジストリにイメージを push します
独自アプリは sinatra を使っています

  • aws ecr get-login –region ap-southeast-2
    • docker login コマンドが表示されたらそのままそれを使ってログインする
  • bundle init
  • vim Gemfile
gem "sinatra"
  • vim app.rb
require 'sinatra'

set :bind, '0.0.0.0'

get '/host' do
  `hostname`
end
  • vim Dockerfile
FROM ruby:latest

ADD . /work
WORKDIR /work
RUN bundle install

CMD ["bundle", "exec", "ruby", "app.rb"]
  • docker build -t simple-sinatra-app .
  • docker tag simple-sinatra-app:latest 123456789012.dkr.ecr.ap-southeast-2.amazonaws.com/simple-sinatra-app:latest
  • docker push 123456789012.dkr.ecr.ap-southeast-2.amazonaws.com/simple-sinatra-app:latest

で OK です
ECS の作成画面はイメージを push したあとで次に進んでください

次にタスクの設定ですが、ここの「ポートマッピング」の設定でホストポートを 0, コンテナポートを 4567 にします
ホストポートを 0 にすることでホスト側のポートを動的に決定することができます
ecs_with_alb2.png

サービスはとりあえずデフォルトのままで OK です
あとで ALB を作成したあとで新しいサービスを作成します

クラスタの設定もそのままで OK です
SSH などは必要に応じて作成してください

あとは確認し問題なければ作成を開始してください

EC2 コンテナホストが所属する VPC ID を確認する

チュートリアル方式で ECS を作成すると EC2 コンテナホストの設定も自動で行われます
ALB を作成する際にどの VPC 配下にいるコンテナを組み込むかという設定が必要になります
なので、ECS が作成できたら EC2 コンテナホストが所属する VPC を確認してください

クラスタ -> デフォルト -> ECS インスタンス -> インスタンスを選択 -> 画面が飛んだら EC2 の画面で VPC ID を確認

EC2 コンテナホストが所属するセキュリティグループの設定を確認する

チュートリアルで作成した場合セキュリティグループも設定を確認したほうがいいと思います
おそらくデフォルトだとインバウンドの設定が以下になっていると思います

  • プロトコル -> TCP
  • ポート範囲 -> 0
  • 送信元 -> 0.0.0.0/0

これだと動的に割り振れれたポートにアクセスできないのでポートの範囲を以下のように変更します

  • ポート範囲 -> 0 - 65535

送信元などは必要に合わせて変更してください

ALB の作成

次に ALB を作成します
EC2 の管理画面画から作成します
ロードバランサーを選択し「ロードバランサーの作成」を選択します
次にロードバランサーのタイプを選択する画面になると思います
ここでは必ず「Application Load Balancer」を選択します

次に ALB の設定です
名前など設定します
ALB が LISTEN するポートは 80 にしています
ecs_with_alb3.png

その下で VPC の設定をします
ここで先程確認した VPC ID を必ず選択するようにしてください
ここが間違うとスケールしたコンテナがうまく ALB にぶら下がってくれません
サブネットを 2 つ以上選択したら次に進みます
ecs_with_alb4.png

セキュリティの設定はとりあえずスルーします

セキュリティグループの設定は今回は default を選択します
アクセス元など絞りたい場合は独自のセキュリティグループを適宜設定してください

ターゲットグループの作成です
新しく ECS のコンテナを管理するターゲットグループを作成しましょう
ポートはアプリが LISTEN する 4567 にしヘルスチェックのパスを今回のアプリが動作する「/host」に変更します
ecs_with_alb5.png

ターゲットの登録は何もしません
すでに ECS コンテナインスタンスが割り当てられていることが確認できると思います
あとは作成すれば OK です

ALB が所属するセキュリティグループの設定を確認する

今回 default を選択しましたがおそらく外部からアクセスできない設定になっていると思います
ALB は 80 番ポートで LISTEN する設定にしているので 80 番ポートでアクセスできる設定を入れてあげましょう

サービスを作成し直す

チュートリアルで作成したサービスを削除し先程作成した ALB を使用するサービスを作成します
クラスタを選択しサービスタブから既存のサービスを削除します
エラーが出て削除できない場合はタスク数が 1 以上になっていると思うので 0 にしてから削除します

削除できたら新規でサービスを作成していきます
サービス名を決定しタスクの数を設定します
タスクの数はとりあえず 1 にしておきましょう (あとからここの数を変更してスケールアウトさせてみます)
ecs_with_alb6.png

ELB の選択で「Application Load Balancer」を選択します
ecs_with_alb7.png

そしてタスクを追加したら以下のように設定します
ターゲットグループは先程作成したターゲットグループを選択しましょう
ecs_with_alb8.png

これで保存しサービスを作成します

動作確認

これでコンテナが ALB にぶら下がり ALB 経由でアプリにアクセスできるようになります
EC2 のロードバランサーのターゲットグループのターゲットタグを確認するとコンテナが「healthy」の状態でぶら下がっているのが確認できると思います
ecs_with_alb9.png

この状態で ALB にデフォルトで振られているドメインにアクセスしてみましょう
http://albforecs-1234567890.ap-southeast-2.elb.amazonaws.com/host (URL はダミーです)

するとコンテナのホスト名が表示できると思います
何度かアクセスしてみて表示されているホスト名が変わらないことを確認してください

コンテナをスケールアウトさせてみる

では、スケールアウトさせてみます
先程作成したサービスのタスクの数を 1 -> 3 くらいまで増やしてみましょう (5 にしても 3 までしか増えなかったのでとりあえず 3 にしました)

これで再度 ALB にアクセスするとホスト名がちょくちょく変わるのが確認できると思います
ALB のターゲットグループを見ると healthy なターゲットが 3 つに増えていることも確認できると思います

最後に

ECS + ALB で web アプリコンテナをスケールアウトしてみました
やってることは単純なのですが概念を理解したり VPC やセキュリティグループまで絡んでくるので結構ハマりポイントが多かったです

ステートレスな Web アプリならこれで単純にスケールアウトできそうです
セッションなどを管理する必要がありアプリがステートフルな場合は、アプリ側でもう少し頑張る必要があるかなと思います

Tips

作成した ECS のリソースでクラスタを削除しようとするとエラーになると思います
その場合は

alb 削除 -> ターゲットグループ削除 -> VPC (インターネットゲートウェイ) -> 使っていた VPC からインターネットゲートウェイをデタッチ

としてからクラスタを削除してみてください

参考サイト

2017年4月9日日曜日

touch: cannot touch '/var/jenkins_home/copy_reference_file.log': Permission denied

概要

docker 上で Jenkins のイメージを動作させる時に発生したので解決策を紹介します

環境

  • CentOS 7.3
  • Jenkins 2.46.1
  • docker 17.03

エラー詳細

実行コマンドは以下の通り

  • docker pull jenkins
  • docker run -p 8080:8080 -p 50000:50000 -v $(pwd):/var/jenkins_home jenkins

で以下のエラーが発生

touch: cannot touch ‘/var/jenkins_home/copy_reference_file.log’Permission denied
Can not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?

解決策

  • chown -R 1000 $(pwd)

$(pwd) の部分は run する時にマウントするパスを指定してください
今回はカレントをマウントしているので上記の用になっています

参考サイト

2017年4月8日土曜日

Jenkins のビルド結果を Slack に通知する方法

概要

Jenkins の結果を Slack に通知してみました

環境

  • CentOS 7.3
  • Jenkins 2.46.1

Slack にアプリ追加

Jenkins アプリを追加しましょう
追加したら通知するチャネルを入力します
jenkins-notify-to-slack1.png

追加が完了すると次の画面で Jenkins 側の設定も詳細に教えてくれます
ここでは Step3 の「Team Domain」「Integration Token」をメモしておきます
jenkins-notify-to-slack2.png

Jenkins のプラグインの追加

プラグインの追加から Slack Notification Plugin を追加します
jenkins-notify-to-slack3.png

インストール後は再起動なしで OK です

ビルドの設定

「ビルド後の処理」で「Slack Notifications」を追加します

どのイベントを通知するか設定することができます
基本は全部チェックで OK だと思います
jenkins-notify-to-slack4.png

カスタムの設定画面を開きます
先程取得した「Team Domain」「Integration Token」を「Team Subdomain」「Integration Token」にそれぞれ入力します
Project Channel に通知するチャネルを入力します
シャープからちゃんと入力するようにしてください
jenkins-notify-to-slack5.png

Include Custom Message にチェックを入れてメッセージを入力すると一緒に Slack に通知することができます

動作確認

ビルドを実行してみましょう
成功するビルドだと以下のように表示されれば OK です
jenkins-notify-to-slack6.png

最後に

Jenkins のビルド結果を Slack に通知する方法を紹介しました
ドキュメントがだいぶ揃っているのでかなり簡単に追加できました

今回はビルドごとにトークンやドメインを設定しましたが、Jenkins の設定で共通の設定を行うことができるので、常に同じチーム、チャネルに通知する場合はそっちで設定したほうが良いと思います

2017年4月7日金曜日

Ruby で docker events を取得して Slack に通知してみた

概要

前回 Ruby で docker のイベント情報を取得する方法を紹介しました
今回は

環境

  • Mac OS X 10.12.3
  • docker 17.03
  • ruby 2.2.3
    • docker-api 1.33.2
    • slack-api 1.2.4

ライブラリのインストール

  • vim Gemfile
gem "docker-api"
gem "slack-api"
  • bundle install

コード

  • vim docker-status-bot.rb
require 'docker'
require 'slack'

@token = "xoxp-..."
Slack.configure {|config| config.token = @token }
Docker.options = { :read_timeout => 900 }

def post(msg)
  params = {
    :token => @token,
    :channel => "#general",
    :username => "docker-status",
    :text => msg,
    :icon_url => 'http://www.gakusmemo.com/wp-content/uploads/2016/08/115HU350-0.jpg'
  }
  puts "post"
  Slack.chat_postMessage params
end

Docker::Event.stream { |event|
  case event.status
  when "start" then
    puts "start"
    msg = "A container #{event.actor.attributes["name"]} is started"
    post msg
  when "destroy" then
    puts "destroy"
    msg = "A container #{event.actor.attributes["name"]} is destroyed"
    post msg
  else
    #puts "status: #{event.action}"
  end
}

token はこちら から取得します

特に説明するポイントはないと思います
Slack に通知するためのメソッドを作成して条件に一致したコンテナのステータスになったら、コンテナ名と共に Slack に通知するだけです

実行

  • bundle exec ruby events.rb

動作確認

docker run --rm alpine date な感じのコンテナを起動すると Slack 上に以下のように通知されると思います

docker-status-slack-notification.png

最後に

Ruby で docker events の情報を Slack に通知するところまでやってみました
今回は Slack に通知する例を紹介しましたが、IFTTT でも MQTT でも何にでも連携することは可能かなと思います

こういうのができると自動化などが捗って良いですね

2017年4月6日木曜日

Ruby で docker events の情報を取得してみた

概要

Ruby から docker API の events 情報を取得してみました
簡単な使い方と Tips を紹介します

環境

  • Mac OS X 10.12.3
  • docker 17.03
  • ruby 2.2.3
    • docker-api 1.33.2

docker-api ライブラリのインストール

  • bundle init
  • vim Gemfile
gem "docker-api"
  • bundle install

イベント情報の待ち受け

  • vim events.rb
require 'docker'

Docker::Event.stream { |event|
  puts event
}
  • bundle exec ruby events.rb

で待受状態になります

挙動確認

docker コマンドでコンテナを立ち上げてみましょう

  • docker run --rm alpine date

すると以下のような出力が表示されると思います

Docker::Event { 1491152422543453362 container create 313a130d7f9395655fb14b7311d9d05cf4a81117f08ea92a1f970d2b67a1f069 (image=alpine, name=blissful_mestorf) }
Docker::Event { 1491152422547042436 container attach 313a130d7f9395655fb14b7311d9d05cf4a81117f08ea92a1f970d2b67a1f069 (image=alpine, name=blissful_mestorf) }
Docker::Event { 1491152422642621021 network connect ac9cb0cf106310f33ae77d528b4aa1d7c47c8a55b91f12b6afe12d00844a40a2 (container=313a130d7f9395655fb14b7311d9d05cf4a81117f08ea92a1f970d2b67a1f069, name=bridge, type=bridge) }
Docker::Event { 1491152423026533369 container start 313a130d7f9395655fb14b7311d9d05cf4a81117f08ea92a1f970d2b67a1f069 (image=alpine, name=blissful_mestorf) }
Docker::Event { 1491152423104603794 container die 313a130d7f9395655fb14b7311d9d05cf4a81117f08ea92a1f970d2b67a1f069 (exitCode=0, image=alpine, name=blissful_mestorf) }
Docker::Event { 1491152423398573335 network disconnect ac9cb0cf106310f33ae77d528b4aa1d7c47c8a55b91f12b6afe12d00844a40a2 (container=313a130d7f9395655fb14b7311d9d05cf4a81117f08ea92a1f970d2b67a1f069, name=bridge, type=bridge) }
Docker::Event { 1491152423554556161 container destroy 313a130d7f9395655fb14b7311d9d05cf4a81117f08ea92a1f970d2b67a1f069 (image=alpine, name=blissful_mestorf) }

結果が docker events と同じイベントの流れになっていることが確認できると思います

Tips

read timeout reached (Docker::Error::TimeoutError) になる場合は以下のように設定し stream を開始します
この場合 900sec x 4 = 3600sec = 1h タイムアウトを待つようになります
なぜか 4 を掛けた値になっているようです

Docker.options = { :read_timeout => 900 }

Docker::Event クラスの各フィールドにアクセスする方法は以下の通りです

require 'docker'

Docker::Event.stream { |event|
  puts "from: #{event.from}"
  puts "id: #{event.id}"
  puts "status: #{event.status}"
  puts "time: #{event.time}"
  puts "type: #{event.type}"
  puts "action: #{event.action}"
  puts "actor: #{event.actor}"
  puts "actor.id: #{event.actor.id}"
  puts "actor.attributes: #{event.actor.attributes}"
  puts "time_nano: #{event.time_nano}"
  puts event
}

デフォルトでは Docker の API はバージョン 1.16 をコールしているそうでs
で、これを編集したい場合はコードを直接編集する必要がありそうです

  • vim vendor/ruby/2.2.3/gems/docker-api-1.33.2/lib/docker/version.rb
API_VERSION = '1.23'

ちゃんとクライアントからバージョンを編集できるように以下の p-r が出ているのですが、どうやら Close されているようです
https://github.com/swipely/docker-api/pull/462

最後に

Ruby から docker API を使ってイベント情報をストリームで取得してみました取得するだけなのでかなり簡単に取得できます
リモートの docker にアクセスする場合は options を設定した感じで Docker.url = 'tcp://example.com:5422' という感じで設定するようです

次回は取得できた情報を Slack にでも通知してみたいと思います

参考サイト

2017年4月5日水曜日

docker events を試してみた

概要

docker events は dockerd 経由でコンテナのイベントがリアルタイムで取得できるコマンドです
今回は実際にコンテナを動かしコマンドがどういう動きをするか試してみました

環境

  • Mac OS X 10.12.3
  • docker 17.03

イベント情報を取得する

コマンドを実行してみましょう

  • docker events

すると、イベントの待受状態になります
この状態で alpine のコンテナを実行してみましょう

  • docker run --rm alpine echo hello

するとイベントの情報がずらずらと流れてくると思います

2017-04-03T01:09:47.401162062+09:00 container create 710b7b90c64b2e62b6e5b74230d74d54402212a9313c2b35000357a9d2ef6239 (image=alpine, name=nostalgic_galileo)
2017-04-03T01:09:47.405128013+09:00 container attach 710b7b90c64b2e62b6e5b74230d74d54402212a9313c2b35000357a9d2ef6239 (image=alpine, name=nostalgic_galileo)
2017-04-03T01:09:47.509438603+09:00 network connect ac9cb0cf106310f33ae77d528b4aa1d7c47c8a55b91f12b6afe12d00844a40a2 (container=710b7b90c64b2e62b6e5b74230d74d54402212a9313c2b35000357a9d2ef6239, name=bridge, type=bridge)
2017-04-03T01:09:47.922252830+09:00 container start 710b7b90c64b2e62b6e5b74230d74d54402212a9313c2b35000357a9d2ef6239 (image=alpine, name=nostalgic_galileo)
2017-04-03T01:09:48.023143257+09:00 container die 710b7b90c64b2e62b6e5b74230d74d54402212a9313c2b35000357a9d2ef6239 (exitCode=0, image=alpine, name=nostalgic_galileo)
2017-04-03T01:09:48.307291714+09:00 network disconnect ac9cb0cf106310f33ae77d528b4aa1d7c47c8a55b91f12b6afe12d00844a40a2 (container=710b7b90c64b2e62b6e5b74230d74d54402212a9313c2b35000357a9d2ef6239, name=bridge, type=bridge)
2017-04-03T01:09:48.460536812+09:00 container destroy 710b7b90c64b2e62b6e5b74230d74d54402212a9313c2b35000357a9d2ef6239 (image=alpine, name=nostalgic_galileo)

ながい!ですがよく見ると

create -> attach -> connect -> start -> die -> disconnect -> destroy

というイベントの流れを確認できると思います

他のイベントを見てみる

先ほどの alpine を実行するコマンドのオプションを変えてみます

  • docker run alpine echo hello

するとイベントが以下のように流れると思います

create -> attach -> connect -> start -> die -> disconnect

最後の disconnect がないのがわかると思います
要するにコンテナは停止したけど削除まではされていないということになります

今度は別のイメージからコンテナを立ち上げてみます

  • docker run --name web1 -d -p 8080:8080 nginx

このコマンドでは

create -> connect -> start

というイベントが発生します
このまま停止、削除とコマンドを実行してみます

  • docker stop web1

kill -> die -> disconnect -> stop

  • docker rm web1

destroy

kill というイベントが一つ増えていました
ちなみに「-d」オプションがないと attach イベントが発生しません

現在あるイベントの一覧は以下の通りです
https://docs.docker.com/engine/reference/commandline/events/

コンテナのイベントの他、イメージやネットワーク、ボリュームごとにイベントが用意されています

最後に

docker events コマンドの挙動を試してみました
イベントは API も提供されているので、これを使って通知や別の処理を実行したりすることができます

次回は ruby でイベント情報を取得してみたいと思います