20191204のdockerに関する記事は10件です。

Docker公式イメージで立てたWordpressにOWASP ZAPで動的スキャンをかけて対応してみた

シーエー・アドバンス Advent Calendar 2019 4日目の記事です。

こんにちは。
シーエー・アドバンス技術統括本部の @asami-H-Ishi です。
開発未経験で入社して4年目ですが、2年くらい脆弱性診断員として業務に携わってきました。
現在は診断チームと開発側の脆弱性診断についての調整業務を担当しています。

はじめに

弊社では脆弱性診断の際にプロキシツールとしてPort Swigger社のBurp Suite Professionalを使用しています。
そこで、昨年のアドベントカレンダーではDocker公式イメージでたてたWordPressにBurpでActiveScanをかけて、結果に対していくつか対策をしてみたという記事を公開しました。

脆弱性診断で使われるプロキシツールには、Burpの他にOWASP ZAPがあります。
こちらは通常の診断業務では使用したことがないのですが、無料のツールで日本語対応していることもあり、業務外でいつか触ってみたいなと思っていました。

そこで昨年やったことをZAPでやってみようと思いました!

この記事の概要

  1. Docker公式イメージでWordpressを立てる
  2. OWASP ZAPで対象サイトをクロール
  3. OWASP ZAPで動的スキャン実行
  4. OWASP ZAPの「アラート」を確認、対応すべき対象を確認
  5. 対策したymlファイルを公開

予想ですが、Burpとほぼ変わらない結果が出るはずなので、ZAPの扱い方のメモみたいになるかと思います。。。
よろしければ最後までお付き合いください。

1.Docker公式イメージでWordpressを立てる

Docker公式のWordpressイメージから、下記ymlファイルを作成します。
(去年とちょっと変わってますね)

docker-compose.yml
version: '3.1'

services:

  wordpress:
    image: wordpress
    restart: always
    ports:
      - 8080:80
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: exampleuser
      WORDPRESS_DB_PASSWORD: examplepass
      WORDPRESS_DB_NAME: exampledb
    volumes:
      - wordpress:/var/www/html

  db:
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_DATABASE: exampledb
      MYSQL_USER: exampleuser
      MYSQL_PASSWORD: examplepass
      MYSQL_RANDOM_ROOT_PASSWORD: '1'
    volumes:
      - db:/var/lib/mysql

volumes:
  wordpress:
  db:

ターミナルで下記コマンドを叩きます(バックグラウンドで実行されます)。

docker-compose up -d

ブラウザで下記にアクセスします。

http://localhost:8080

すると、WordPressのインストール画面が表示されます。
WordPressのインストールとログインが完了すると、WordPressのダッシュボードに入ることができます。
せっかくなので、ダッシュボードから何か新規投稿して見てみましょう。
管理者としてログインしている状況になるので、ログアウトしてあらためて http://localhost:8080 にアクセスしてみてください。
スクリーンショット 2019-11-30 16.11.55.png
こんな感じで表示されてるかと思います(サイト名を適当に「AAA」にしたのでこんなんなってます)。

ここまでで、Docker公式イメージでWordpressを立てるところまで終わりました。

2. OWASP ZAPで対象サイトをクロール

次に、OWASP ZAPを立ち上げます。
スクリーンショット 2019-11-30 17.00.44.png
こんな感じ。

OWASP ZAPのインストールやプロキシ設定等はMac版のOWASP ZAPで脆弱性チェックの設定が参考になります。

わたしは通常業務で下記の設定をしているので、そこに合わせてZAPとブラウザの設定をしました。

Chromeでの設定
スクリーンショット 2019-12-04 10.09.54.png

ZAPでのプロキシ設定の手順
スクリーンショット 2019-12-04 10.01.44.png

プロキシ設定を済ませたブラウザから http://localhost:8080 にアクセスしようとすると、なぜかサイト一覧に表示されなかったので(ここの調査は時間ないのでしませんでした!)、ifconfigでローカルIPアドレスを調べて、Wordpress管理画面の設定>一般WordPress アドレス (URL)サイトアドレス (URL)localhost部分をローカルIPアドレスに書き換えました。
そうすると、ブラウザからhttp://<ローカルIPアドレス>:8080でアクセスし、サイト内の遷移をした時にすべてローカルIPアドレスでURLを取得することができます。

さて、気を取り直して、プロキシ設定を済ませたブラウザからhttp://<ローカルIPアドレス>:8080でアクセスすると、ZAPのサイトタブ一覧にURLが表示される=ちゃんとZAPで通信がとれていることを確認します。
そのまま、サイトをクロールします。
すべてのページにアクセスし終わったら、クロール完了です。
(ZAPのスパイダー機能で済ませようかと思いましたが、Wordpressの?p=1みたいなURLはスパイダーでは取得できないようなので、通常の脆弱性診断と同様クロールする形にしました)

この後、今クロールした診断対象URLたちに動的スキャンを実施したいのですが、Wordpress公式のページや、gravatarのページ(いずれも本番サイト)への遷移もあったので、動的スキャンの対象をローカルで立てたWordpressのみに絞る必要があります。
そこで、サイト一覧の http://<ローカルIPアドレス>:8080 を右クリックしてコンテキストに含める>New Contextを押します(画像のとおり)。
スクリーンショット 2019-11-30 17.23.49.png

セッション・プロパティ画面が出てくるので、「OK」を押すと、対象のドメインをコンテキストに追加することができます。

コンテキスト一覧に http://<ローカルIPアドレス>:8080 が含まれていることを確認したら、動的スキャンに進みます。

3.OWASP ZAPで動的スキャン実行

OWASP ZAPの下半分に「履歴」「検索」「アラート」「アウトプット」と並び、右端に緑の十字ボタンがあります。
そこを押すと隠れている機能があるので、そこから動的スキャンを押します。
スクリーンショット 2019-11-30 17.56.02.png

①「新規スキャン」ボタンを押す
②動的スキャンのスコープ画面が出てくるので、「参照」ボタンを押してノード選択
③コンテキストに設定した対象ドメインを選択
④選択ボタンを押す
⑤「スキャンを開始」ボタンを押す
スクリーンショット 2019-12-04 19.01.23.png

これで動的スキャンを実施できます。
結果は「アラート」タブで確認できます。

4. OWASP ZAPの「アラート」を確認、対応すべき対象を確認

今回の結果はこんな感じでした。
スクリーンショット 2019-12-04 20.07.57.png

ひとつずつ見ていきます。

X-Frame-Optionsヘッダーの欠如

説明部分には

「クリックジャッキング」攻撃を防止するためのX-Frame-OptionsヘッダーがHTTPレスポンスに含まれていません。

と書かれています。
これに対応するにはX-Frame-Optionヘッダの設定が必要です。
WordPressの機能にはSAMEORIGINで設定されているので、同様にサイト全体に対して設定します。

CookieのHttpOnly属性が未設定

説明部分には

cookieにHttpOnly属性が設定されていないため、JavaScriptによるcookieへのアクセスが可能です。
悪意のあるスクリプトが本ページで実行可能な場合、cookieにアクセスして別のサイトへcookieを送信することができます。
もしスクリプトがアクセス可能なcookieがセッション管理用である場合、セッションハイジャック攻撃が成立する可能性があります。

と書かれていました。
ただ、今回のWordpressに関しては管理画面を利用するユーザ以外にログイン機能を利用する想定ではないため、対応外とします。

WebブラウザのXSS防止機能が有効になっていません。

説明部分には

Web ブラウザのXSS防止機能が有効になっていない、またはWebサーバからのHTTPレスポンスヘッダ 'X-XSS-Protection' が無効になっています。

と書かれていました。
これは対応しておく必要があります。

X-Content-Type-Optionsヘッダの設定ミス

説明がなぜかこれだけ英語でしたw

The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.

X−Content−Type−Optionsヘッダの設定をnosniff にしましょう、という内容になります(ざっくり)。

上記はいずれも、Docker公式イメージでたてたWordPressにBurpでActiveScanをかけて、結果に対していくつか対策をしてみたで対応した内容になりますので、よかったらこちらの記事も見てみてください!

5. 対策したymlファイルを公開

以上の結果から、今回対応したい内容は

  • レスポンスヘッダに下記設定を入れたい
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Option: SAMEORIGIN

ヘッダ系の指摘は脆弱性診断をしていて必ず出てくるものなので、WordPressをカスタマイズする過程で先んじて対応しておきたいです。
設定は、Dockerfileに書いておいて、WordPressを立ち上げる時にその設定で立ち上がるようにします。
下記が設定全部載せのDockerfileとymlファイルです。
ご参考になれば幸いです。

dockerfile
FROM wordpress:5.0.0-apache

# phpのバージョン情報を隠せた
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
RUN sed -i -e "s|expose_php = On|expose_php = Off|" "$PHP_INI_DIR/php.ini"

# apacheのバージョンを隠す
RUN echo "ServerSignature Off" >> /etc/apache2/apache2.conf
RUN echo "ServerTokens Prod" >> /etc/apache2/apache2.conf

# 不要ファイルの削除
RUN rm -rf /usr/src/wordpress/readme.html /usr/src/wordpress/xmlrpc.php

# meta
RUN bash -c 'echo -e "remove_action(\"wp_head\",\"wp_generator\");" >> /usr/src/wordpress/wp-content/themes/twentynineteen/functions.php'
RUN bash -c 'echo -e "foreach ( array( \"rss2_head\", \"commentsrss2_head\", \"rss_head\", \"rdf_header\",    \"atom_head\", \"comments_atom_head\", \"opml_head\", \"app_head\" ) as \$action ) {    if ( has_action( \$action, \"the_generator\" ) )        remove_action( \$action, \"the_generator\" );}" >> /usr/src/wordpress/wp-content/themes/twentynineteen/functions.php'

# バージョン消す
RUN bash -c 'echo -e "function vc_remove_wp_ver_css_js(\$src){if(strpos(\$src,\"ver=\".get_bloginfo(\"version\")))\$src=remove_query_arg(\"ver\",\$src);return \$src;}" >> /usr/src/wordpress/wp-includes/functions.php'
RUN bash -c 'echo -e "add_filter(\"style_loader_src\", \"vc_remove_wp_ver_css_js\", 9999);" >> /usr/src/wordpress/wp-includes/functions.php'
RUN bash -c 'echo -e "add_filter(\"script_loader_src\", \"vc_remove_wp_ver_css_js\", 9999);" >> /usr/src/wordpress/wp-includes/functions.php'

# ヘッダの設定をする
RUN bash -c 'echo "Header append X-XSS-Protection: \"1; mode=block\"" >> /etc/apache2/apache2.conf'
RUN bash -c 'echo "Header append X-Content-Type-Options: nosniff" >> /etc/apache2/apache2.conf'
RUN bash -c 'echo "Header append X-Frame-Options: SAMEORIGIN" >> /etc/apache2/apache2.conf'
RUN bash -c 'a2enmod headers'
docker-compose.yml
version: '3'
services:
  wordpress:
    build: .
    ports: 
      - "3000:80"
    environment:
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: mysql_user
      WORDPRESS_DB_PASSWORD: mysql_pw
    restart: always
    depends_on:
      - mysql
  mysql:
    image: mysql:5.7
    environment: 
      MYSQL_ROOT_PASSWORD: root_pw
      MYSQL_DATABASE: wordpress
      MYSQL_USER: mysql_user
      MYSQL_PASSWORD: mysql_pw
    restart: always
    volumes:
      - mysql_data:/var/lib/mysql
volumes:
    mysql_data:

さいごに

検証の時間が足りず、去年の対応と全く同じ形になってしまいましたが、ご参考になれば幸いです。
また、もしかすると最新のWordpressではもっといい設定があるかもしれないので、何かご存知の方がいらしたらご教示いただけると嬉しいです!(時間に余裕があれば、クリスマスまでに自分でも検証してこっそり追記したいと思います)

よいクリスマス、よい年末年始をお過ごしください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

大丈夫、俺もDocker分かってないから一緒にやろう。

はじめに:挫折しても良い

プログラミング学習って、用語の難しさとか学習コストの高さとかでやっぱ大変なことですよ。
時代が変わって、ハードルは低くなったが未だに「難しそう」という固定概念はあるし、やった事がないことなんて大抵難しいそうなもんです。。
いまだにプログラミングってやつには全然なれなくて、めっちゃ悔しいわけだが、アウトプットをさせて欲しいです。

公式の説明文でポルナレフ状態になろう(困惑)

まず最初にみてもらいたいのは、docker社の公式HPでの説明文だ。
え、エンジニアは英語にアレルギー感じちゃダメだって・・?

「そうかもしれないが、今じゃない。俺はDockerが触りてえんだよ゛お゛お゛ぉ゛お゛!゛!゛!゛ん゛あ゛あ゛あ゛あ゛あ゛ぁ゛ぁ゛あ゛あ゛!゛!゛!゛!゛。」
ってことで翻訳にかけてみた。

スクリーンショット 2019-11-24 0.08.24.png

翻訳してみると、こんな感じの解釈になる。
コイツァ...頭が痛い。。まるでセンターの現代文みたいだ。

名称未設定ファイル (57).jpg

この時点だと俺もDockerに対する認識はこんな感じだ。

名称未設定ファイル (59).jpg

でも、これじゃあ困る。
dockerを使えると嬉しくなるっぽいので、なんとかdockerとは何者なのか、
何ができるのか知っておきたい。

Dockerの要点をまとめてみた。

まず、具体的作業を説明する前に一通りやってみた上でDockerについてまとめてみた。

---概要---
・コンテナ型の仮想環境を作成、配布、実行するためのプラットフォーム
・仮想マシン(VirtualBoxなど)と比較されやすい
・Linuxのコンテナ技術を使ったもの
・ホストマシン(PC)のカーネル(OSの中核)を利用し、プロセス(実行状態にあるプログラム?)やユーザなどを隔離することで、あたかも別のマシンが動いているかのように動かすことができる。

---メリット---
・コード化されたファイルを共有することで、どこでも誰でも同じ環境が作れる。
・作成した環境を配布しやすい。
・スクラップ&ビルドが容易にできる。
・軽量で高速に起動、停止などが可能

---やった作業(復習)---
参考記事:https://qiita.com/wMETAw/items/8cb41425a5d0bdace2df
詳細説明:https://qiita.com/azul915/items/5b7063cbc80192343fc0

① dataonlyコンテナを生成
② webserverコンテナ・dbserverコンテナの生成
③ Docker Composeによる複数コンテナの起動
④ データの確認
⑤ MySQL接続

今の時点で知っておいて欲しいこと

・自分のパソコンの中に、もう一個パソコンを擬似的に作ることをこれからする。
・イメージ的には「スマホでゲームボーイのゲームを起動する」みたいなことをするっぽい。
・メリットは速さ・手軽さ

具体的手順1 "ファイル構成"

名称未設定ファイル (60).jpg

$ mkdir dataonly
dataonlyという名前のディレクトリ作成

$ touch Dataonly
Dataonlyファイルの作成

$ touch Gemfile
Gemfileファイルの作成(Railsのgemについては下記リンク参照)
https://qiita.com/kamohicokamo/items/ded4cad5be1778547640

$ touch Gemfile.lock
Gemfile.lockファイルの作成

$ touch docker-compose.yml
docker-compose.ymlファイルの作成

※ディレクトリとファイルの違い
https://webliker.info/60828/

ディレクトリやファイル作成に困るようになら、右クリックでファイル作成・フォルダ作成でもいい。

具体的手順2 "各ファイルの中身について"

①ファイルを開く
$ open /dataonly/Dockerfile
②下記テキストをコピペ。
Dockerfileの中身を編集

Dockerfile.
FROM ruby:2.3.3
# RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
RUN apt-get update -qq
RUN apt-get install -y build-essential 
RUN apt-get install -y libpq-dev
RUN apt-get install -y nodejs
# ワーキングディレクトリの設定
RUN mkdir /myapp
WORKDIR /myapp
# gemfileを追加する
ADD Gemfile /myapp/Gemfile
ADD Gemfile.lock /myapp/Gemfile.lock
# gemfileのinstall
RUN bundle install
ADD . /myapp

書き終わったら、内容を保存するためにテキストエディタ上で保存(winならctrl+S/macならcommand+S)
①と②の作業を下記ファイルでも実行(ただし、コピペする内容は下記の内容でやる。)

$ open dataonly/Gemfile
Gemfileの中身を編集

Gemfile.
source 'https://rubygems.org'
gem 'rails', '5.0.0.1'

$ open dataonly/Gemfile
docker-compose.ymlの中身を編集

docker-compose.yml
db:
  image: mysql:5.7
  environment:
    MYSQL_ROOT_PASSWORD: root
web:
  build: .
  command: bundle exec rails s -p 3000 -b '0.0.0.0'
  volumes:
    - .:/myapp
  ports:
    - "3000:3000"
  links:
    - db

具体的手順3 "Railsでプロジェクトを作成"

rails newでプロジェクトを作成

$ docker-compose run web rails new . --force --database=mysql --skip-bundle

docker-composeを走らせて、rails newする。

※docker-composeとは、複数のコンテナから成るサービスを構築・実行する手順を自動的にし、管理を容易にする機能。

※rails newについて→https://www.sejuku.net/blog/14144
後で再度ビルドを行う必要がある為、--skip-bundleでビルドをスキップする。

具体的手順4 "DBhostの修正を行う"

$ open config/database.yml

元の内容を下記内容に書き換える

database.yml
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: root

password: root # docker-compose.ymlのMYSQL_ROOT_PASSWORD
host: db # docker-compose.ymlのサービス名

具体的手順5 "Docker上でRailsを起動させる"

コンテナをビルド(https://wa3.i-3-i.info/word12775.html)
$ docker-compose build

コンテナの一斉起動
$ docker-compose up
●なぜ一斉起動が必要か?(https://knowledge.sakura.ad.jp/5736/)

・DB作成
$ docker-compose run web rails db:create

・通常、RailsでDBを作成しようとするなら、下記のURLを利用。
https://techacademy.jp/magazine/7207

確認(きちんとできているか)
http://localhost:3000/

具体的手順6 "中身を入れて動作を確認する"

・scaffoldでCRUDを生成してみる

●scaffoldとは何か
https://techacademy.jp/magazine/7204

●CRUDについて
CRUD(クラッド)とは、ほとんど全てのコンピュータソフトウェアが持つ永続性の4つの基本機能のイニシャルを並べた用語。その4つとは、Create(生成)、Read(読み取り)、Update(更新)、Delete(削除)である。

scaffoldする。
$ docker-compose run web rails g scaffold users name:string

migrationする
$ docker-compose run web rails db:migrate

●migrationとは何か
https://qiita.com/right1121/items/0a54ba76dc4261702d1e

・動作確認しよう!
http://localhost:3000/users

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

dockerコンテナの中で立ち上げたGUIアプリをmacに表示してみる

この記事で出来ること

dockerコンテナの中でGUIアプリを立ち上げて、mac(※1)に表示する
具体的には、VSCodeを立ち上げれるようになる

(※1 macに限らずともX Window Systemがインストールされている環境ならどこでも使えるはず)

背景

Dockerを使って開発するのがかなり一般的になってきました。かく言う私も、仕事や趣味でDockerばかり使っています。
Dockerfileさえ書いてしまえば、いつでも同じ環境を構築できるのでとても便利です。

ただDockerを使うことでやりづらくなる点もあります。それがデバッグ作業です。

私はIDEのデバッガーをよく使います。ブレークポイントを自由自在に置くことができ、現時点での変数やコールスタックが一覧で表示されるのでとても捗ります。ターミナルで行うデバッグも良いですが、使い勝手の良さでは圧倒的にIDEのデバッガーが優れていると思います。

私の知る限り、IDEのデバッガーはIDEがインストールされているホストマシンの環境をまず見に行きます。そのため、初期設定のままではDockerコンテナの中で立ち上げたプロセスを知る術がなく、デバッグのしようがありません。

もちろん、きちんと設定をすればDockeコンテナのプロセスのデバッグをすることができます。
が、設定、めんどくさいときないですか?
設定ができない言語やIDEだったらもう諦めるしかありません。

そんな時、Dockerコンテナの中でエディタを立ち上げちゃいましょう

方針

sshX11 Forwardingを使います。

Dockerコンテナ内で立ち上げたGUIアプリが、コンテナ内のX Window Systemではなく、ホストコンピューターのX Window Systemに対して描画指示を送るようにします。

手順

今回は手始めにVSCodeをDockerコンテナの中で立ち上げたいと思います。

※以下、ローカル開発することを前提としたコードです。セキュリティの保証はできかねますので十分注意してください。

準備

macには標準でX Window Systemがインストールされていないみたいです。
なので、インストールします。

brew update
brew cask install xquartz

UbuntuなどのLinuxにはおそらくデフォルトでインストールされてるんじゃないかなと思います。

dockerのバージョンはこちら

$ docker -v
Docker version 19.03.5, build 633a0ea

1. SSHの公開鍵と秘密鍵を作成

Dockerコンテナとssh通信を行うのでそこで使う鍵を作ります。
今回は id_rsa_for_docker_sshと言う名前で作ります。

$ ssh-keygen -t rsa -b 4096
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/machisuke/.ssh/id_rsa): /Users/machisuke/.ssh/id_rsa_for_docker_ssh

2. Dockerfileを作る

sshやX11 Forwarding、VSCodeの起動に必要なライブラリを入れます。
他のエディタやGUIツールを使う場合は、必要に応じて書き換えてください。
Dockerfileはこちらを参考にさせていただきました。

machisukeと言う名前でユーザーを追加しており、パスワードはpasswordとしておきました。ここも適宜変えてください。

Dockerfile
FROM ubuntu:18.04

RUN apt-get update && apt-get install -y --no-install-recommends \
        ca-certificates\
        sudo \
        openssh-server \
        wget \
        libnotify4 \
        gnupg \
        libxkbfile1 \
        libsecret-1-0 \
        libgtk-3-0 \
        libxss1 \
        libnss3 \
        libx11-xcb-dev \
        libxtst6 \
        libasound2 \
        xauth \
        iputils-ping \
    && \
    apt-get clean  && \
    rm -rf /var/lib/apt/lists/*

### User settings ###
ARG NB_USER=machisuke
ENV NB_USER ${NB_USER}
# HOST_ID should be same as host's user id.
ARG HOST_UID
ENV NB_UID ${HOST_UID}

# Add user and set password. (ここではユーザーのパスワードは"password"とします)
RUN useradd -m -G sudo -s /bin/bash -u $NB_UID $NB_USER && \
    echo "${NB_USER}:password" | chpasswd

RUN echo "Defaults visiblepw"             >> /etc/sudoers
RUN echo "${NB_USER} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
### End ###

### SSHでDockerにアクセスするための設定
ARG AUTHORIZED_KEYS
RUN ( echo "#!/bin/bash"; \
      echo "echo ${AUTHORIZED_KEYS} > /home/${NB_USER}/.ssh/authorized_keys"; \
      echo "chown -R ${NB_USER}:${NB_USER} /home/${NB_USER}"; \
      echo "chmod 600 /home/${NB_USER}/.ssh/authorized_keys"; \
      echo "service ssh start"; \
      echo "tail -f /dev/null"; ) > /home/${NB_USER}/entrypoint.sh && \
    chmod +x /home/${NB_USER}/entrypoint.sh && \
    sed -i.bak 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config && \
    echo "PermitUserEnvironment yes" >> /etc/ssh/sshd_config && \
    echo "X11UseLocalhost no" >> /etc/ssh/sshd_config && \
    mkdir /home/${NB_USER}/.ssh && chmod 700 /home/${NB_USER}/.ssh && \
    ( echo "Host *"; \
      echo "    StrictHostKeyChecking no"; \
      echo "    UserKnownHostsFile=/dev/null"; ) > /home/${NB_USER}/.ssh/config
# Disable annoying warning about sudo
RUN touch ~/.sudo_as_admin_successful

# ユーザーの切り替え
USER $NB_USER
ENV LANG=ja_JP.UTF-8


# VSCodeのダウンロードとインストール
RUN cd /home/${NB_USER} && \
    wget --no-verbose https://go.microsoft.com/fwlink/?LinkID=760868 -O vscode.deb && \
    sudo apt-get install -y --no-install-recommends ./vscode.deb

EXPOSE 22

# SSHサーバーを起動
CMD ["sudo","bash","-c", "bash ~/entrypoint.sh"]

3. 各種スクリプトの用意

作成したスクリプトは全て同じディレクトリにおいてください。

  • Dockerイメージビルド用

公開鍵を変数として渡しています。

docker_build.sh
#!/usr/bin/env bash
PUB_KEY=$(cat ~/.ssh/id_rsa_for_docker_ssh.pub)
IMAGE_NAME="docker_ssh"
docker build \
    --build-arg  HOST_UID="$(id -u)" \
    --build-arg  AUTHORIZED_KEYS="${PUB_KEY}" \
    -t ${IMAGE_NAME} \
    .
  • Dockerコンテナ起動用

このコマンドを実行すれば、buildも同時に走るようにします。
sshで使うコンテナの22番ポートをホストマシンの8022番にマッピングします。

docker_run.sh
#!/usr/bin/env bash
IMAGE_NAME="docker_ssh"
CONTAINER_NAME=${IMAGE_NAME}
bash ./docker_build.sh
docker run --name=${CONTAINER_NAME} --rm -it -p 8022:22 ${IMAGE_NAME}
  • X11 forwarding用

ファイルにするまでもないですが、覚えるのも大変なので。
docker_run.shにより、Dockerコンテナの22番ポートはlocalhostの8022番にマッピングされています。

docker_connect.sh
#!/usr/bin/env bash
ssh -p 8022 -X -o StrictHostKeyChecking=no machisuke@localhost

4. Dockerコンテナの起動

$ bash ./docker_run.sh
Sending build context to Docker daemon  66.56kB
Step 1/17 : FROM ubuntu:18.04
 ---> 775349758637
Step 2/17 : RUN apt-get update && apt-get install -y --no-install-recommends         sudo         openssh-server         wget         libnotify4         gnupg         libxkbfile1         libsecret-1-0         libgtk-3-0         libxss1         libnss3         libx11-xcb-dev         libxtst6         libasound2         ca-certificates        &&     apt-get clean  &&     rm -rf /var/lib/apt/lists/*
 ---> Using cache
 ---> 9d3d8a65d0d7

(省略)

Step 16/17 : EXPOSE 22
 ---> Using cache
 ---> 3d096b9c1108
Step 17/17 : CMD ["sudo","bash","-c", "bash ~/entrypoint.sh"]
 ---> Using cache
 ---> c9e835fc358a
Successfully built c9e835fc358a
Successfully tagged docker_ssh:latest
 * Starting OpenBSD Secure Shell server sshd               [ OK ]

5. XQuartsのシェルからX11 forwardingとvscodeの起動

  • XQuartsを起動して、メニューバーからターミナルを開きます。

  • docker_connect.shを実行 パスワードはDockerfileに設定した passwordです。
XQuartsのターミナル上からvscodeを起動
$ ./docker_connect.sh 
machisuke@localhost's password: 
Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.9.184-linuxkit x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

This system has been minimized by removing packages and content that are
not required on a system that users do not log into.

To restore this content, you can run the 'unminimize' command.
Last login: Wed Dec  4 07:14:55 2019 from 172.17.0.1
machisuke@1ff26b7a58a3:~$ code .
  • 無事起動できました!お疲れ様です。

image.png

終わりに

この記事では、Dockerコンテナの中でGUIアプリを立ち上げるための最低限必要なことを共有させていただきました。みなさんのデバッグ作業の助けになっていれば、幸いです。

もちろん、エディタだけでなく、どんなGUIアプリもこの方法で表示することができると思いますので、興味のある方は挑戦してみてください。

この記事を参考に、Dockerコンテナ内でエディタを起動しようと思っている人は、下記の点に注意していただければと思います。

  • コンテナを起動する度にエディタの設定がリセットされてしまう。
    • プラグインなど、エディタの設定を恒久的に維持するためには改修が必要
  • デバッガーによっては追加の設定が必要
    • C++のデバッグ環境の構築は大変でした。(私はClionを使ってました)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerの環境変数設定でのしょうもないミス

しょうもないミスでもハマったら書いてけぇ?という先輩のお告げから

Dockerからazure blob storageに接続するだけなのに以下のようなエラーでつまずき、
試行回数を増やしたりしたが…

error-message
Client-Request-ID=XXXX
Retry policy did not allow for a retry: , HTTP status code=Unknown,
Exception=HTTPSConnectionPool(host='XXX.blob.core.windows.net', port=443): Max retries exceeded with url: XXX.txt
(Caused by NewConnectionError('<urllib3.connection.VerifiedHTTPSConnection object at 0x7f8b1c804a50>:
 Failed to establish a new connection: [Errno -2] Name or service not known')).

実際に問題だったのはDockerfileの環境変数設定の書き方。

Dockerfile
# 誤
ENV AZURE_ACCOUNT_NAME = 'XXX'
# 正
ENV AZURE_ACCOUNT_NAME='XXX'

癖で空白を入れてしまうと「= XXX」で環境変数が登録されてしまいます。:stuck_out_tongue_closed_eyes:
接続できないのも当たり前

Azure & Docker初心者が故にどっちが原因か特定するのに時間がかかりました…
しょーもない…

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【サーバーサイド一式】Docker + Rails + Circle CI + Terraformでインフラをコードで環境構築 & ECSへ自動コンテナデプロイ【前半】

流行りの技術を使いたい我々は

おはようございます。今年もアドヴェントな感情が舞い散る季節になってきました。皆さん如何お過ごしでしょうか? 本番環境と仲良くやっていますでしょうか? 自分は非常に犬猿な仲になりつつあります。いいんです、人生はいつだって本番なのですから(?)

さて、巷のエンジニア達によるエンジニアの為のエンジニア論では、もはや

  • (主にサーバーサイドの)アプリをDockerを使って環境構築して
  • terraformでインフラの構成をコード化して
  • (Kubernetesなどのコンテナオーケストレーションツールを用いて)なるべく開発環境との差異が無いままコンテナデプロイをして
  • CircleCIでテストからデプロイまで自動化

といった事がサーバーサイドエンジニアとしては求められている、いや最早知らないようではやっていくのは難しい、といったご意見が結構な頻度で見られたりします。

(そういったご意見の是非はまた別の機会に預けるとして)だとしたら初心者のサーバーサイド志望のエンジニアさん達の為には、これらの

  • Docker
  • CircleCI
  • Terraform
  • コンテナデプロイ(今回はECS)

といった要素をバーーーーーーーーとチュートリアル形式で学んでいくのが手っ取り早いのでは無いか(そして自分の理解の整理にもなる)と思い立ちました! どうでしょうか?
つまりこの記事で現時点でナウいサーバーサイドの技術が一通り触れちゃう!ということです!
自分はその膨大さに今若干後悔しています。

まあ、じゃ、書きます!まーた多分長いよ。。。

※過去の長い記事たち
【初心者向け】丁寧すぎるRails『アソシエーション』チュートリアル【幾ら何でも】【完璧にわかる】
【初心者向け】railsアプリをherokuを使って確実にデプロイする方法【決定版】

明日(12/5)ワンマンライブなんでよかったらきてください
MV
予約はこちら

目次

目次
全体像を把握しよう
Dockerを用いてRailsの環境を構築
CircleCIでCIの設定
Terraformを使うための準備
前半はここで終わり

この記事を読むにあたっての前提

  • gitとgithubを使った事がある
  • Dockerをなんとなく理解している
  • Railsも触ったことある(触ったことなくてもできるかも)
  • AWSのコンソールでEC2やVPCを立てた事がある

もちろん、それ以外の方も是非是非挑戦してみてください。理解なんて後から帳尻合わせればいいんです。プログラミングは体で覚えるものです(?)

できるようになること

railsアプリをDockerで構築できて、pushしたタイミングでCircleCIが走って、予めTerraformで構築しておいたAWS環境(ECS)に自動デプロイする事ができるようになる!

注意してほしい事

  • DBはsqliteです。RDSとつないでません。
  • Webサーバもpumaで兼用してます。nginxなどのWebサーバーとつないでません =>これらはまた次回やろうと思います!

自分の環境

  • Ruby 2.6.3
  • Rails 6.0.1
  • Docker 19.03.4
  • Git 2.14.1
  • Terraform 0.12.8

全体像を把握しよう

目次
全体像を把握しよう←今ココ
Dockerを用いてRailsの環境を構築
CircleCIでCIの設定
Terraformを使うための準備
前半はここで終わり

様々なツールやサービスを繋げていく事は、全体像の把握がとても大事になってきます。
まず大きい流れを確認しましょう。時系列順に列挙すると以下となります。

アプリが自動でコンテナデプロイされるまでの流れ

(0.TerraformでAWS上のリソースを定義)
1.RailsアプリをGithubにpush
2.CircleCIでCI開始
3.CIでDockerイメージをビルド
4.DockerイメージをECRへpush
5.ECSのTaskDefinitionを更新
6.CIでmigration
7.アプリがデプロイされる!

画像に表す以下となります。

image.png
画像1

ざっくり各ツールの役割も解説すると

terraform

インフラストラクチャ定義ツール。
クラウド上のリソースを定義ファイルの状態になるように生成・操作してくれる。
画面上でポチポチやってたインフラの操作をコードにできる。

rails

デプロイしたいアプリそのもの。

github

バージョン管理ツール。
アプリとCircleCIをつなぐ為のハブみたいな役割も担っている。

CircleCI

ビルド/テスト/デプロイなどについて自動実行できるサービス。
これを用いて色々面倒くさい事を自動化しよう。

Docker

アプリを動かす為の環境が一式詰まった仮想化プラットホーム。
ここでrailsが動く事はだいたい担保されてるので、Railsアプリが入ったDockerコンテナをそのまま本番>環境にのっけたい

ECR

コンテナイメージをprivateな環境に格納しておけるサービス。
Railsアプリの環境を入れたDockerコンテナイメージをここにぶっ込みたい。

ECS

dockerコンテナを通して処理をしたりサービスを立ち上げたりと行った挙動を、EC2上で容易に行うためのAWSのサービス。EC2上での操作は全部AWSでやってくれるので、デプロイが簡単(のはずだがそうでもない)。

となります!非常に盛りだくさんですね。
はい、いつも通り順番に解説していきますのでご安心ください。

ではまずはDockerを用いて、Railsアプリを動かせる環境を作っていきましょう!

Dockerを用いてRailsの環境を構築

目次
全体像を把握しよう
Dockerを用いてRailsの環境を構築←今ココ
CircleCIでCIの設定
Terraformを使うための準備
前半はここで終わり

とりあえずローカル環境で一度作ります。
terraformとrailsアプリを一つのディレクトリにまとめちゃいたいので、 terraform_ecs_deploy
というディレクトリを作っちゃいましょう(githubのリポジトリは別にするので注意してください!)

ターミナル
$ mkdir terraform_ecs_deploy
$ cd terraform_ecs_deploy
$ rails new terraform_ecs_app --skip-javascript # webpackerは今回いらないです
$ cd terraform_ecs_app
$ rails db:migrate

さて、このタイミングでRailsアプリをDocker化します。
Dockerfileを作り、公式のRubyのイメージを取得し、以下のように書きましょう。
(今回簡単なDockerfileしか用意していないのはご了承ください。)

Dockerのインストールはこちら

Dockerfile
# 公式のイメージから取得
FROM ruby:2.6.3

# Dockerfile内部で使える変数として定義
ARG RAILS_ENV
ARG RAILS_MASTER_KEY

# コンテナ内のルートとする変数を/appと定義
ENV APP_ROOT /app

# 環境変数化
ENV RAILS_ENV ${RAILS_ENV}
ENV RAILS_MASTER_KEY ${RAILS_MASTER_KEY}

# コンテナ内のルートとする。
WORKDIR $APP_ROOT

# ローカルのGemfile, Gemfile.lockをコンテナ内のルートへコピー
ADD Gemfile $APP_ROOT
ADD Gemfile.lock $APP_ROOT

# bundle install実行。
# (バージョンのエラーが出る為、一応bundler 2.0.2を指定)
RUN \
    gem install bundler:2.0.2 && \ 
    bundle install && \
    rm -rf ~/.gem

# バンドルインストールが終わってから他のファイルもコンテナ内へコピー
ADD . $APP_ROOT

# 本番環境の場合プロダクション
RUN if ["${RAILS_ENV}" = "production"]; then bundle exec rails assets:precompile; else export RAILS_ENV=development; fi

# ポート3000番を公開
EXPOSE 3000
CMD ["rails", "server", "-b", "0.0.0.0"]

では、このDockerfileを元にDockerコンテナをbuildし、railsを立ち上げてみます。

ターミナル
$ docker build -t terraform_ecs_app:latest . # -tでタグを指定
$ docker run -it -p 3000:3000 terraform_ecs_app:latest # -it

docker run時のオプションは以下の通りです。

-i, --interactive・・・コンテナのSTDINにアタッチする
-t, --tty・・・疑似ターミナルを割り当てる

このようになっていたらOKです。

image.png

http://localhost:3000にアクセスするとこのようにおなじみの画面になっているはずです。

image.png

※注意! docker run -it -p 3000:3000 terraform_ecs_app:latestが通らない場合

もしここで
~~~
docker: you are not authorized to perform this operation: server returned 401.
See 'docker run --help'.
~~~

このようなエラーが出る場合は、ターミナルで以下のコマンドを打ち、環境変数を設定してください。

ターミナル
export DOCKER_CONTENT_TRUST=0

これにより、Dockerイメージが改ざんされていないか、という整合性判定をスキップでき、上のコマンドが通るようになるはずです。

Docker Content Trust(DCT)について

Railsの詳細設定

さて、今回Railsの話が本題では無いので、機能的には簡素なもので良いです。なので、
- 表示用のトップ画面
- ALB(Application Load Balancer)HealthCheck用に、jsonでステータスOKが帰ってくるURL

の二つだけ実装できれば良いです。(もちろんALBとヘルスチェックについても後述します。)

ターミナル
$ rails g controller top index 
$ rails g controller health_check

topコントローラ はアクションとビューが自動生成されているはずなので、health_checkコントローラ だけ変更します。

app/controllers/health_check_controller.rb
class HealthCheckController < ApplicationController
  # ALBにステータスokを返す為のアクション
  def index
    render json: '{ "status": "ok" }'
  end
end

ルーティングも変更します。

config/routes.rb
Rails.application.routes.draw do
  root to: 'top#index'
  resources :health_check, only: [:index]
end

それぞれ以下のように表示されればOKです。

http://localhost:3000
image.png

http://localhost:3000/health_check
image.png

また、本番環境で tmp/pidstmp/socketsが作成されない事がある為、念のため.gitignoreに以下の記述を追加しましょう。

.gitignore
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
#   git config --global core.excludesfile '~/.gitignore_global'

# Ignore bundler config.
/.bundle

# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal
/db/*.sqlite3-*

# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep
# ===============追加================
!/tmp/pids
!/tmp/sockets
# =============ここまで===============
# Ignore uploaded files in development.
/storage/*
!/storage/.keep

/public/assets
.byebug_history

# Ignore master key for decrypting credentials and more.
/config/master.key

最低限の機能が実装できたので、githubにpushしましょう。

githubにrailsアプリをpush

今回はgitとgihubの解説は省略します。

gitのインストール
githubの使い方

カレントディレクトリがrailsアプリなのを確認してから、以下のコマンドを打ってください。
(自分は今回リポジトリ名は terraform_ecs_app としています。)

ターミナル
$ git init
$ git commit -m "initial commit"
$ git remote add origin リポジトリのURL
$ git push origin master

githubのリポジトリが以下のようになっていたらOKです。

image.png

それではいよいよCircleCIのCI設定をおこなっていきましょう!

CircleCIでCIの設定

目次
全体像を把握しよう
Dockerを用いてRailsの環境を構築
CircleCIでCIの設定←今ココ
Terraformを使うための準備
前半はここで終わり

CircleCIとは、Saas型のCI/CDサービスであり、CI/CDでやることの基本であるビルドとテストとデプロイの3点が自動化可能なサービスです。

今回はこちらを使ってtestとデプロイ(ECRへのpush)の自動化を行なっていきたいと思います。

以下、CircleCIならではの特徴や料金についてはこちらをご参照ください。
いまさらだけどCircleCIに入門したので分かりやすくまとめてみた

CircleCIの導入

image.png

↓こちらのURLの右上あたりにあるLoginをクリックし、Log in with Githubを押してログインしましょう。
すると、自動的にgithubと紐づくはずなので、CIの設定画面まで行きましょう。
CircleCI

image.png

こちらの画像のように、Add Projectsを押すと、自分のgithubのリポジトリが出てくるので、
その中から先ほど作成したterraform_ecs_appを選択します。Set Up Projectを押しましょう。

スクリーンショット_2019-12-04_10_39_28.png

すると、プロジェクトの設定画面になると思います。
Operating SystemLinux
LanguageRubyのままで大丈夫です。

そして、そのすぐ下に英語でチュートリアルが書いてあります。和訳すると

  1. .circleciという名前のフォルダーを作成し、config.ymlファイルを追加します(ファイルパスが.circleci/config.ymlになるように)。
  2. config.ymlsample.ymlの内容を入力します(以下を参照)
  3. sample.ymlを更新して、プロジェクトの構成を反映します。
  4. この変更をGitHubにプッシュします。
  5. 構築を始めましょう!これにより、CircleCIでプロジェクトが起動し、Webhookが作業の更新をリッスンします。

とのことです。実際にそのすぐ下に、あらかじめCircleCIが用意してくれてたrails用の設定ファイルsample.ymlがあります。
指示通り、これをコピペして、ローカル環境で.circleciというフォルダを作り、その中にconfig.ymlという名前のファイルを作り、そこに貼り付ければOKです。

ただし、これだとrubyのバージョンが違うのと、テストフレームワークがrspecになっているので、今回はrailsデフォルトのテストフレームワークであるminitestに変えた以下のものにしてください。

.circleci/config.yml
# Ruby CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-ruby/ for more details
#
version: 2
# jobsの中にタスクを定義。一番下のworkflowのjobsのなかで定義したタスクを使う。
jobs:
  # buildという名前のタスク定義
  build:
    docker:
      # specify the version you desire here
      - image: circleci/ruby:2.6.3-node-browsers


      # Specify service dependencies here if necessary
      # CircleCI maintains a library of pre-built images
      # documented at https://circleci.com/docs/2.0/circleci-images/
      # - image: circleci/postgres:9.4

    working_directory: ~/repo

    # 実際の処理内容
    steps:
      - checkout

      # Download and cache dependencies
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "Gemfile.lock" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-
      # runのたびに実行
      - run:
          name: install dependencies
          command: |
            gem install bundler -v 2.0.2
            bundle install --jobs=4 --retry=3 --path vendor/bundle

      - save_cache:
          paths:
            - ./vendor/bundle
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}

      # Database setup
      - run: bundle exec rake db:create
      - run: bundle exec rake db:schema:load

      # run tests
      - run:
          name: run tests
          command: |
            DISABLE_SPRING=true bundle exec rails test


      # collect reports
      - store_test_results:
          path: /tmp/test-results
      - store_artifacts:
          path: /tmp/test-results
          destination: test-results

ここまでできたら、先ほどのCircleCIの設定画面に戻り、5の右側にあるStartbuildingを押してください。CircleCIが動き出します。

スクリーンショット_2019-12-04_11_04_15.png

こうすることによって次回以降はpushされたタイミングでCircleCIがJobし始めます!

(ちなみにroutes.rbを変えたせいでtestが失敗してしまうので、該当のtestをコメントアウトするか、正しい記述に直してからpushとbuildをしてください)

image.png

成功したらcircleCIのJOBSのタブの表示がこのようになります。(一番上の緑色がそうです。)

image.png

さて、ここまできたらようやくTerraformに入ります!!

Terraformを使うための準備

目次
全体像を把握しよう
Dockerを用いてRailsの環境を構築
CircleCIでCIの設定
Terraformを使うための準備←今ココ
前半はここで終わり

Terraformのインストール

Terraform公式
Terraformを用いることで、今まで手続き的だったインフラの工程をコード化することできます。

homebrewでもインストールできますが、Terraformのバージョンマネージャであるtfenvを使うのがおすすめです。バージョンアップに追従しやすいです。
まずはtfenv自体をインストールしましょう。

ターミナル
$ brew install tfenv
$ tfenv --version
tfenv 1.0.1

list-reomteコマンドでインストール可能なTerraformのバージョンを確認できます。
ここでは、0.12.8をインストールしましょう。

ターミナル
$ tfenv list-remote
.
.
0.12.8
$ tfenv install 0.12.8
$ terraform -v
Terraform v0.12.8

TerraformでAWSを扱うためのIAMユーザの設定

terraformでAWSを扱うにはIAMユーザのACCESS KEY、SECRET KEY(とDEFAULT REGION)が必要です。

ここだけはterraformの管理下におけないので、AWSのコンソールでIAMユーザを作成しし、アクセスキーとシークレットキーを発行してください。

IAMユーザの作成

発行できたら、ターミナルで以下のようにアクセスキーとシークレットキーを環境変数に設定してください。

ターミナル
 $ export AWS_ACCESS_KEY_ID=AKIxxxxxxxxx
 $ export AWS_SECRET_ACCESS_KEY=wJalxxxxxxxxxxxxxxxx
 $ export AWS_DEFAULT_REGION=ap-northeast-1

(PCの電源落としたり、ターミナル落としたりしたら消えてしまうのでもう一度入力し直してください。)

今回自分はAdministratorAccess ポリシーをアタッチしたIAMユーザのアクセスキーを用いていますが、こちら相当に強力な権限なので扱いには注意してください。
(間違ってもGitHubなどで公開してはダメです!)
AdministratorAccessポリシー以外では、権限不足で Terraform の実行が失敗することがあるので、その場合はエラーメッセージを参考に、必要な権限を付与しましょう。

これでTerraformを扱う準備ができました!

terraform用のgithubのリポジトリの作成

アプリ開発やCIとインフラ構成は別の話なので、リポジトリを分けるのがセオリーです。
今回はterraform_ecsという名前でgithubのリポジトリを作成します。

秘匿情報を扱うので、プライベートリポジトリにすることに注意してください。

image.png

さて、terraformのディレクトリ構造に関する考えは色々あるのですが
https://dev.classmethod.jp/devops/directory-layout-bestpractice-in-terraform/
https://qiita.com/anfangd/items/1b84f69fa2a4f8a29fbc
https://future-architect.github.io/articles/20190903/

今回は簡単のため環境だけディレクトリを分けて、その中に全てのtfファイルを突っ込む方式でいこうと思います。

terraform_ecs_deploy
├──terraform_ecs_app
└──terraform_ecs
    ├──prod
    └──stg

また、今回はステージング環境の構築は行いません!ほぼ同じことをやるだけなので、一度productionで構築できたらすぐできちゃうと思います。

では早速、terraform用のディレクトリを作成しましょう。

ターミナル
$ cd .. # railsアプリにいた場合
$ pwd
/Users/matsumotokazuki/Desktop/terraform_ecs_deploy # terraform_ecs_deployにいることを確認。
$ mkdir terraform_ecs
$ mkdir terraform_ecs/prod
$ cd terraform_ecs/prod

このようなディレクトリ構成になっていれば大丈夫です。

terraform_ecs_deploy
├──terraform_ecs_app
└──terraform_ecs
    └──prod # カレントディレクトリ

ここで、githubにpushする前に、terraformのインフラ構成ファイルを表すterraform.tfstateや秘匿情報を表すterraform.tfvarsなどをgitの管理下から除外します。(こちらも後述します)

ターミナル
$touch .gitignore
terraform_ecs/prod/.gitignore
/.terraform/*
/terraform.tfvars
/terraform.tfstate

それではpushしましょう。

ターミナル
$ cd .. # terraform_ecsディレクトリに移動
$ git init
$ git add -A
$ git commit -m "initial commit"
$ git remote add origin リポジトリ名
$ git push origin master

image.png

前半はここで終わり

目次
全体像を把握しよう
Dockerを用いてRailsの環境を構築
CircleCIでCIの設定
Terraformを使うための準備
前半はここで終わり←今ココ

諸々の大体の準備が終わった時点で一旦区切ります!
後半からいよいよTerraformを使ってインフラを構築していきますので、気長にお待ちください。

後半はコチラ(にする予定)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerのMySQLがエラーで起動できない

dockerで開発をしている際に、
Could not write unix socket lock file /var/run/mysqld/mysqld.sock.lock
といったエラーが出てMySQLが起動できなくなってしまったので、
その対処法をメモ。

エラーにある意味どおり、書き込みができないとあるので、
MySQLが正常に終了出来ずにlockが外れなかったのが原因かな?
とりあえずコンテナとイメージの削除をしてみる。

# コンテナとイメージを一括削除
$ docker container prune
$ docker image prune

# ビルドしてコンテナ立ち上げ
docker-compose up --build

なおった
イメージの容量が30GB超えてて驚いた

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPやRubyとNode.jsを同一コンテナ内に手っ取り早く管理したい人のためのマルチステージビルド

この記事は Docker Advent Calendar 2019 3日目の記事です。
抜けていたので急遽書きます。

背景

PHPやRuby、Node.jsを同一コンテナ内で管理したいときありますよね。
Laravel + VueやRails + React等。

そんなときDockerfileで個別にNode.jsをインストールしている記事を時々見かけます。
そんなあなたにマルチステージビルドがおすすめです!

DockerfileでPHPコンテナとNode.jsコンテナをマルチステージビルドで構築する例

FROM node:12.13.0-alpine as node

FROM php:7.3-fpm-alpine

## PDOなどインストールは省略

WORKDIR /var/www/html

# マルチステージビルドでphp-fpmコンテナにnodeとnpmを追加する
COPY --from=node /usr/local/bin/node /usr/local/bin/
COPY --from=node /usr/local/lib/node_modules/ /usr/local/lib/node_modules/
RUN ln -s /usr/local/bin/node /usr/local/bin/nodejs \
    && ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
    && ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npx

Node.jsコンテナからnodeとnpmのバイナリをコピーし、シンボリックリンクを貼るだけ!
簡単でお手軽で早いです!

参考

Nuxt.js2.10とLaravel(6.0)を同一ディレクトリで動かすnuxt-laravelをDockerで環境構築

明日は@mei_9961さんの記事です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jenkinsでAmazon ECR上のイメージを使う

はじめに

こんにちは、SREエンジニアやっています。@hayaosatoです。
今回はjenkinsでのCI/CDの際に自作のDockerイメージを利用したい機会があると思います。
jenkinsのDockerプラグインを利用してDockerfileをビルドさせて実行することも出来るのですが、配布されたイメージを利用したイメージを使いたいなど、レジストリ上のイメージを利用したいケースがあると思います。
そのようなケースに対応できるよう、jenkinsでレジストリから取得したイメージを使う方法をECRに特化して書いていこうと思います。

Dockerfileからのビルド

一応、独自のイメージを利用するケースとしてDockerfileからのビルド方法について書いていきます。
Jenkinsのdockerプラグインでは、 agentにdockerfileを指定することができ、デフォルトではアプリのルートディレクトリのDockerfileで作成されます。

pipeline {
    agent { dockerfile true }
    stages {
        stage('Test') {
            steps {
                sh "echo 'hoge'"
            }
        }
    }
}

コンテナレジストリ上のイメージ

さて、本題のコンテナレジストリ(ECR)上のイメージを利用する方法ですが、以下の通りになります。

pipeline{
    agent any
    stages{
        stage('Hoge') {
            steps{
                script{
                    docker.withRegistry("https://${AWSアカウントID}.dkr.ecr.${ECRのリージョン}.amazonaws.com") {
                        sh("`aws ecr get-login --no-include-email --region ${リージョン}`")
                        docker.image('my-custom-image').inside {
                          sh("echo 'hoge'")
                        }
                    }
                }
            }
        }
    }
}

前提として、JenkinsでのコンテナレジストリはデフォルトでDockerHubを利用するようになっています。
DockerプラグインのwithRegistryというレジストリを指定して作業することのできる機能があるのですが、
これを利用することでDockerHub以外のレジストリを利用することができます。
今回はECRを利用するのでwithRegistryの引数にレジストリのURLを入れるのですが、ここではリポジトリのURIでは無いので注意してください。
また、docker loginも必要になってきますので、コマンドでloginを行います。
すると、DockerHubでのイメージの利用と同様にdocker.imageを利用することができるようになります。

最後に

今回はJenkinsでECR上のコンテナイメージを利用する方法を書きました。
Jenkinsおじさん、ぜひ試してみてください。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerコンテナ作成時にError response from daemonが発生する

エラー内容

Dockerコンテナ作成時に下記エラーが発生し、作成できない

Error response from daemon: OCI runtime create failed: container_linux.go:348: starting container process caused "process_linux.go:297: copying bootstrap data to pipe caused \"write init-p: broken pipe\"": unknown.

環境

Ubuntu14.04
docker-ce 18.06.3

原因

Ubuntuカーネルのバージョンが低い(3x kernel)ため、
Docker(18.06.3)に対応できない

対応

1.UbuntuのOSバージョンアップ(稼働中はお勧めしない)
2.Dockerのダウングレード

→2で対応
指定したDockerバージョンは「18.06.1~ce~3-0~ubuntu」
手順はこちらを参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GitHub Actions と GitHub Packages で Docker image を配布する

Docker Hub から GitHub Packages へ

Git tag やブランチをトリガーに Docker image を自動でビルドして Docker registry で公開・配布したいとなると、まず Docker Hub の利用が候補として考えられます。

あれだけの機能を無料で提供してくれている Docker Hub 開発チームには本当に感謝しているのですが、一点だけ不満があります。実行時間です。Docker Hub はビルドの開始が遅く、実行時間も結構かかりがちです。

これの解決策として外部サービスで Docker image をビルドして、そこから Registry に image を Push することが考えられます。外部サービスには色々な選択肢がありますが、今回は GitHub Actions を選びました。ビルド行程を GitHub Actions で行えば Docker Hub よりは速そう(速かった)です。おそらく CircleCI とかであればもっと速いでしょうが、ビルド行程の管理のしやすさも合わせて総合的に考えると GitHub Actions が便利だと思います。

この記事では GitHub Actions と GitHub Packages で Docker image をビルド・配布する方法について紹介します。(ついでに GitHub Actions から Docker Hub にも Push します。)

ちなみに GitHub Packages は旧名 GitHub Package Registry です。

mdBook Docker image

rust-lang/mdBook の alpine base Docker image を以下のリポジトリで管理しており GitHub Actions で image をビルドして Docker Hub と GitHub Packages へ push しています。

以下のようなリリースフローとなっています。

  • master branch へ push すると latest tag のイメージを公開
  • Release を作成すると release tag のイメージを公開
  • Pull Request ではビルドはするが push はしない

alpine:3.10 base のイメージと rust:alpine3.10 ベースのイメージ、この2種類を並列にビルドして配布しています。

リポジトリの概要

ファイル一覧

リポジトリのファイル一覧です。(記事に必要なものだけを抜粋)

.github/
└── workflows
    └── push.yml
.hadolint.yaml
Dockerfile

Dockerfile の中身です。

ARG BASE_IMAGE

FROM rust:slim-buster AS builder

ARG MDBOOK_VERSION
ENV MDBOOK_VERSION ${MDBOOK_VERSION:-0.3.5}
ENV ARC="x86_64-unknown-linux-musl"
RUN apt-get update && \
    apt-get install --no-install-recommends -y \
    musl-tools && \
    rustup target add "${ARC}" && \
    cargo install mdbook --version "${MDBOOK_VERSION}" --target "${ARC}"

FROM ${BASE_IMAGE}

SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
COPY --from=builder /usr/local/cargo/bin/mdbook /usr/bin/mdbook
WORKDIR /book
ENTRYPOINT [ "/usr/bin/mdbook" ]

GitHub Actions workflow です。

name: Push Docker Image

on:
  push:
    branches:
    - master
    paths-ignore:
    - '**.md'
  release:
    types: [published]
  pull_request:
    types: [opened, synchronize]
    paths-ignore:
    - '**.md'

env:
  DOCKER_BASE_NAME: docker.pkg.github.com/${{ github.repository }}/mdbook
  DOCKER_HUB_BASE_NAME: peaceiris/mdbook

jobs:
  hadolint:
    runs-on: macos-latest
    steps:
    - uses: actions/checkout@v1
      with:
        fetch-depth: 1
    - run: brew install hadolint
    - run: hadolint ./Dockerfile

  push:
    runs-on: ubuntu-18.04
    needs: hadolint
    strategy:
      matrix:
        baseimage: ['alpine:3.10', 'rust:alpine3.10']
    steps:

    - uses: actions/checkout@v1
      with:
        fetch-depth: 1

    - name: Set env
      run: |
        if [ "${{ github.event_name }}" = 'release' ]; then
            export TAG_NAME="${{ github.event.release.tag_name }}"
        else
            export TAG_NAME="latest"
        fi
        if [ "${{ startsWith( matrix.baseimage, 'rust:alpine') }}" = "true" ]; then
            export TAG_NAME="${TAG_NAME}-rust"
        fi
        echo "::set-env name=PKG_TAG::${DOCKER_BASE_NAME}:${TAG_NAME}"
        echo "::set-env name=HUB_TAG::${DOCKER_HUB_BASE_NAME}:${TAG_NAME}"

    - name: Build ${{ matrix.baseimage }} base image
      run: |
        docker build . -t "${PKG_TAG}" --build-arg BASE_IMAGE="${{ matrix.baseimage }}"
        docker tag "${PKG_TAG}" "${HUB_TAG}"

    - name: Print mdBook version
      run: |
        docker run --rm ${PKG_TAG} --version

    - name: Login to Registries
      if: github.event_name != 'pull_request'
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        DOCKER_HUB_TOKEN: ${{ secrets.DOCKER_HUB_TOKEN }}
      run: |
        echo "${GITHUB_TOKEN}" | docker login docker.pkg.github.com -u peaceiris --password-stdin
        echo "${DOCKER_HUB_TOKEN}" | docker login -u peaceiris --password-stdin

    - name: Push to GitHub Packages
      if: github.event_name != 'pull_request'
      run: docker push "${PKG_TAG}"

    - name: Push to Docker Hub
      if: github.event_name != 'pull_request'
      run: docker push "${HUB_TAG}"

GitHub Packages

# Pull alpine:3.10 base image
docker pull docker.pkg.github.com/peaceiris/docker-mdbook/mdbook:latest
docker pull docker.pkg.github.com/peaceiris/docker-mdbook/mdbook:v0.3.5

# Pull rust:alpine3.10 base image
docker pull docker.pkg.github.com/peaceiris/docker-mdbook/mdbook:latest-rust
docker pull docker.pkg.github.com/peaceiris/docker-mdbook/mdbook:v0.3.5-rust

Docker Hub

Autobuild 機能を OFF にしています。リポジトリをリンクしているだけです。

# Pull alpine:3.10 base image
docker pull peaceiris/mdbook:latest
docker pull peaceiris/mdbook:v0.3.5

# Pull rust:alpine3.10 base image
docker pull peaceiris/mdbook:latest-rust
docker pull peaceiris/mdbook:v0.3.5-rust

GitHub Actions Workflow の解説

トリガー

  • master branch から latest tag
  • Release event から vx.x.x tag
  • Pull Request はビルドテスト用 (Push しない)

master へ push すると latest tag のイメージが公開されます。v0.3.5 タグでリリースを作成すると v0.3.5 tag のイメージが公開されます。Pull Request ではビルドだけを行います。

on:
  push:
    branches:
    - master
    paths-ignore:
    - '**.md'
  release:
    types: [published]
  pull_request:
    types: [opened, synchronize]
    paths-ignore:
    - '**.md'

workflow 全体で使う環境変数

Workflow 全体で使う環境変数を定義しており、各 Job で共有できます。

env:
  DOCKER_BASE_NAME: docker.pkg.github.com/${{ github.repository }}/mdbook
  DOCKER_HUB_BASE_NAME: peaceiris/mdbook

hadolint job

hadolint:
  runs-on: macos-latest
  steps:
  - uses: actions/checkout@v1
    with:
      fetch-depth: 1
  - run: brew install hadolint
  - run: hadolint ./Dockerfile

hadolintDockerfile をチェックしています。hadolint の Docker image も公開されていますが .hadolint.yaml を上手く読み込むことができなかったので macOS 仮想環境に homebrew でインストールした hadolint を使っています。

Push job

push:
  runs-on: ubuntu-18.04
  needs: hadolint
  strategy:
    matrix:
      baseimage: ['alpine:3.10', 'rust:alpine3.10']
  steps:

hadolint job が正常終了した場合に push job を並列に実行しています。

mdBook は mdbook test というサブコマンドで Book 中に記述された Rust コードの実行テストができます。そのテストには Rust の実行環境が必要なので alpine:3.10 base のイメージだけでなく rust:alpine3.10 base のイメージもビルド・公開するようにしています。それぞれのイメージを順番にビルドするとビルド時間が2倍になってしまうので matrix で記述することにより並列実行しています。

- name: Set env
  run: |
    if [ "${{ github.event_name }}" = 'release' ]; then
        export TAG_NAME="${{ github.event.release.tag_name }}"
    else
        export TAG_NAME="latest"
    fi
    if [ "${{ startsWith( matrix.baseimage, 'rust:alpine') }}" = "true" ]; then
        export TAG_NAME="${TAG_NAME}-rust"
    fi
    echo "::set-env name=PKG_TAG::${DOCKER_BASE_NAME}:${TAG_NAME}"
    echo "::set-env name=HUB_TAG::${DOCKER_HUB_BASE_NAME}:${TAG_NAME}"

イメージのタグを設定しています。set-env で定義した環境変数は以降の step で利用できるようになります。

- name: Build ${{ matrix.baseimage }} base image
  run: |
    docker build . -t "${PKG_TAG}" --build-arg BASE_IMAGE="${{ matrix.baseimage }}"
    docker tag "${PKG_TAG}" "${HUB_TAG}"

イメージのビルド step です。 matrix.baseimage からベースイメージを取得しています。

- name: Print mdBook version
  run: |
    docker run --rm ${PKG_TAG} --version

なにか実行テストが可能であれば push する前にしておくと良いでしょう。ここでは単純に mdbook のバージョンを出力しています。

- name: Login to Registries
  if: github.event_name != 'pull_request'
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    DOCKER_HUB_TOKEN: ${{ secrets.DOCKER_HUB_TOKEN }}
  run: |
    echo "${GITHUB_TOKEN}" | docker login docker.pkg.github.com -u peaceiris --password-stdin
    echo "${DOCKER_HUB_TOKEN}" | docker login -u peaceiris --password-stdin

GitHub Packages と Docker Hub へログインします。GitHub Actions beta v2 のリリース当初は Packages への Push に Personal access token が必要でしたが、コミュニティの意見が反映され、今では自動生成される GITHUB_TOKEN で Packages への Push ができるようになりました。

DOCKER_HUB_TOKEN は Docker Hub で生成した Personal access token です。Secret 変数として事前に設定する必要があります。

Pull Request の時はスキップしたいので if: github.event_name != 'pull_request' としています。

- name: Push to GitHub Packages
  if: github.event_name != 'pull_request'
  run: docker push "${PKG_TAG}"

- name: Push to Docker Hub
  if: github.event_name != 'pull_request'
  run: docker push "${HUB_TAG}"

最後に GitHub Packages と Docker Hub へイメージを Push しています。ひとつの step にまとめてもいいと思いますが、それぞれの実行時間を計測するためにあえて二つの step に分けています。(どちらも同じ実行時間でした。)

Pull Request の時はスキップしたいので if: github.event_name != 'pull_request' としています。

定期的にイメージをアップデートする

alpine:3.10.x の更新を反映するために定期的にイメージをアップデートする Workflow を別に作りました。合計12個の Job が並列に実行されて Job 1個の時間で完了します。

今は単純に version を列挙していますが何か上手いやり方があるかもしれません。

name: Update Docker Image

on:
  schedule:
    - cron: '11 11 */31 * *'

env:
  DOCKER_BASE_NAME: docker.pkg.github.com/${{ github.repository }}/mdbook
  DOCKER_HUB_BASE_NAME: peaceiris/mdbook

jobs:

  update:
    runs-on: ubuntu-18.04
    strategy:
      matrix:
        baseimage: ['alpine:3.10', 'rust:alpine3.10']
        version: ['0.3.0', '0.3.1', '0.3.2', '0.3.3', '0.3.4', '0.3.5']
    steps:

    - uses: actions/checkout@v1
      with:
        fetch-depth: 1
        ref: "v${{ matrix.version }}"

    - name: Set env
      run: |
        export TAG_NAME="v${{ matrix.version }}"
        if [ "${{ startsWith( matrix.baseimage, 'rust:alpine') }}" = "true" ]; then
            export TAG_NAME="${TAG_NAME}-rust"
        fi
        echo "::set-env name=PKG_TAG::${DOCKER_BASE_NAME}:${TAG_NAME}"
        echo "::set-env name=HUB_TAG::${DOCKER_HUB_BASE_NAME}:${TAG_NAME}"

    - name: Build ${{ matrix.baseimage }} base image
      run: |
        docker build . -t "${PKG_TAG}" \
            --build-arg BASE_IMAGE="${{ matrix.baseimage }}"
        docker tag "${PKG_TAG}" "${HUB_TAG}"

    - run: docker run --rm ${PKG_TAG} --version
    - run: docker images

    - name: Login to Registries
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        DOCKER_HUB_TOKEN: ${{ secrets.DOCKER_HUB_TOKEN }}
      run: |
        echo "${GITHUB_TOKEN}" | docker login docker.pkg.github.com -u peaceiris --password-stdin
        echo "${DOCKER_HUB_TOKEN}" | docker login -u peaceiris --password-stdin

    - name: Push to GitHub Packages
      run: docker push "${PKG_TAG}"

    - name: Push to Docker Hub
      run: docker push "${HUB_TAG}"

まとめ

GitHub Packages と GitHub Actions の組み合わせは Docker Hub 以上に高速で柔軟性がありました。ビルド行程だけ GitHub Actions に任せてもいいですし、もう Docker Hub を使わずに GitHub Packages と GitHub Actions だけで完結させても問題ないでしょう。

以上です。ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む