- 投稿日:2019-12-04T20:29:48+09:00
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でやってみようと思いました!
この記事の概要
- Docker公式イメージでWordpressを立てる
- OWASP ZAPで対象サイトをクロール
- OWASP ZAPで動的スキャン実行
- OWASP ZAPの「アラート」を確認、対応すべき対象を確認
- 対策したymlファイルを公開
予想ですが、Burpとほぼ変わらない結果が出るはずなので、ZAPの扱い方のメモみたいになるかと思います。。。
よろしければ最後までお付き合いください。1.Docker公式イメージでWordpressを立てる
Docker公式のWordpressイメージから、下記ymlファイルを作成します。
(去年とちょっと変わってますね)docker-compose.ymlversion: '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
にアクセスしてみてください。
こんな感じで表示されてるかと思います(サイト名を適当に「AAA」にしたのでこんなんなってます)。ここまでで、Docker公式イメージでWordpressを立てるところまで終わりました。
2. OWASP ZAPで対象サイトをクロール
OWASP ZAPのインストールやプロキシ設定等はMac版のOWASP ZAPで脆弱性チェックの設定が参考になります。
わたしは通常業務で下記の設定をしているので、そこに合わせてZAPとブラウザの設定をしました。
プロキシ設定を済ませたブラウザから
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
を押します(画像のとおり)。
セッション・プロパティ画面が出てくるので、「OK」を押すと、対象のドメインをコンテキストに追加することができます。
コンテキスト一覧に
http://<ローカルIPアドレス>:8080
が含まれていることを確認したら、動的スキャンに進みます。3.OWASP ZAPで動的スキャン実行
OWASP ZAPの下半分に「履歴」「検索」「アラート」「アウトプット」と並び、右端に緑の十字ボタンがあります。
そこを押すと隠れている機能があるので、そこから動的スキャン
を押します。
①「新規スキャン」ボタンを押す
②動的スキャンのスコープ画面が出てくるので、「参照」ボタンを押してノード選択
③コンテキストに設定した対象ドメインを選択
④選択ボタンを押す
⑤「スキャンを開始」ボタンを押す
これで動的スキャンを実施できます。
結果は「アラート」タブで確認できます。4. OWASP ZAPの「アラート」を確認、対応すべき対象を確認
ひとつずつ見ていきます。
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.ymlversion: '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ではもっといい設定があるかもしれないので、何かご存知の方がいらしたらご教示いただけると嬉しいです!(時間に余裕があれば、クリスマスまでに自分でも検証してこっそり追記したいと思います)よいクリスマス、よい年末年始をお過ごしください!
- 投稿日:2019-12-04T20:03:41+09:00
大丈夫、俺もDocker分かってないから一緒にやろう。
はじめに:挫折しても良い
プログラミング学習って、用語の難しさとか学習コストの高さとかでやっぱ大変なことですよ。
時代が変わって、ハードルは低くなったが未だに「難しそう」という固定概念はあるし、やった事がないことなんて大抵難しいそうなもんです。。
いまだにプログラミングってやつには全然なれなくて、めっちゃ悔しいわけだが、アウトプットをさせて欲しいです。公式の説明文でポルナレフ状態になろう(困惑)
まず最初にみてもらいたいのは、docker社の公式HPでの説明文だ。
え、エンジニアは英語にアレルギー感じちゃダメだって・・?「そうかもしれないが、今じゃない。俺はDockerが触りてえんだよ゛お゛お゛ぉ゛お゛!゛!゛!゛ん゛あ゛あ゛あ゛あ゛あ゛ぁ゛ぁ゛あ゛あ゛!゛!゛!゛!゛。」
ってことで翻訳にかけてみた。翻訳してみると、こんな感じの解釈になる。
コイツァ...頭が痛い。。まるでセンターの現代文みたいだ。この時点だと俺もDockerに対する認識はこんな感じだ。
でも、これじゃあ困る。
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 "ファイル構成"
$ 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.ymldb: 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.ymladapter: mysql2 encoding: utf8 pool: 5 username: rootpassword: 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
- 投稿日:2019-12-04T18:45:58+09:00
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コンテナの中でエディタを立ち上げちゃいましょう
方針
ssh
のX11 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 633a0ea1. 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_ssh2. Dockerfileを作る
sshやX11 Forwarding、VSCodeの起動に必要なライブラリを入れます。
他のエディタやGUIツールを使う場合は、必要に応じて書き換えてください。
Dockerfileはこちらを参考にさせていただきました。
machisuke
と言う名前でユーザーを追加しており、パスワードはpassword
としておきました。ここも適宜変えてください。DockerfileFROM 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@localhost4. 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 .
- 無事起動できました!お疲れ様です。
終わりに
この記事では、Dockerコンテナの中でGUIアプリを立ち上げるための最低限必要なことを共有させていただきました。みなさんのデバッグ作業の助けになっていれば、幸いです。
もちろん、エディタだけでなく、どんなGUIアプリもこの方法で表示することができると思いますので、興味のある方は挑戦してみてください。
この記事を参考に、Dockerコンテナ内でエディタを起動しようと思っている人は、下記の点に注意していただければと思います。
- コンテナを起動する度にエディタの設定がリセットされてしまう。
- プラグインなど、エディタの設定を恒久的に維持するためには改修が必要
- デバッガーによっては追加の設定が必要
- C++のデバッグ環境の構築は大変でした。(私はClionを使ってました)
- 投稿日:2019-12-04T16:36:23+09:00
Dockerの環境変数設定でのしょうもないミス
しょうもないミスでもハマったら書いてけぇ?という先輩のお告げから
Dockerからazure blob storageに接続するだけなのに以下のようなエラーでつまずき、
試行回数を増やしたりしたが…error-messageClient-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」で環境変数が登録されてしまいます。
接続できないのも当たり前Azure & Docker初心者が故にどっちが原因か特定するのに時間がかかりました…
しょーもない…
- 投稿日:2019-12-04T14:39:49+09:00
【サーバーサイド一式】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.アプリがデプロイされる!画像に表す以下となります。
ざっくり各ツールの役割も解説すると
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しか用意していないのはご了承ください。)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です。
http://localhost:3000
にアクセスするとこのようにおなじみの画面になっているはずです。※注意! 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イメージが改ざんされていないか、という整合性判定をスキップでき、上のコマンドが通るようになるはずです。
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.rbclass HealthCheckController < ApplicationController # ALBにステータスokを返す為のアクション def index render json: '{ "status": "ok" }' end endルーティングも変更します。
config/routes.rbRails.application.routes.draw do root to: 'top#index' resources :health_check, only: [:index] endそれぞれ以下のように表示されればOKです。
http://localhost:3000/health_check
また、本番環境で
tmp/pids
とtmp/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の解説は省略します。
カレントディレクトリがrailsアプリなのを確認してから、以下のコマンドを打ってください。
(自分は今回リポジトリ名はterraform_ecs_app
としています。)ターミナル$ git init $ git commit -m "initial commit" $ git remote add origin リポジトリのURL $ git push origin mastergithubのリポジトリが以下のようになっていたらOKです。
それではいよいよCircleCIのCI設定をおこなっていきましょう!
CircleCIでCIの設定
目次 全体像を把握しよう Dockerを用いてRailsの環境を構築 CircleCIでCIの設定←今ココ Terraformを使うための準備 前半はここで終わり CircleCIとは、Saas型のCI/CDサービスであり、CI/CDでやることの基本であるビルドとテストとデプロイの3点が自動化可能なサービスです。
今回はこちらを使ってtestとデプロイ(ECRへのpush)の自動化を行なっていきたいと思います。
以下、CircleCIならではの特徴や料金についてはこちらをご参照ください。
いまさらだけどCircleCIに入門したので分かりやすくまとめてみたCircleCIの導入
↓こちらのURLの右上あたりにある
Login
をクリックし、Log in with Github
を押してログインしましょう。
すると、自動的にgithubと紐づくはずなので、CIの設定画面まで行きましょう。
CircleCIこちらの画像のように、
Add Projects
を押すと、自分のgithubのリポジトリが出てくるので、
その中から先ほど作成したterraform_ecs_app
を選択します。Set Up Project
を押しましょう。すると、プロジェクトの設定画面になると思います。
Operating System
はLinux
Language
もRuby
のままで大丈夫です。そして、そのすぐ下に英語でチュートリアルが書いてあります。和訳すると
.circleci
という名前のフォルダーを作成し、config.yml
ファイルを追加します(ファイルパスが.circleci/config.yml
になるように)。config.yml
にsample.yml
の内容を入力します(以下を参照)sample.yml
を更新して、プロジェクトの構成を反映します。- この変更を
GitHub
にプッシュします。- 構築を始めましょう!これにより、
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が動き出します。こうすることによって次回以降はpushされたタイミングでCircleCIがJobし始めます!
(ちなみにroutes.rbを変えたせいでtestが失敗してしまうので、該当のtestをコメントアウトするか、正しい記述に直してからpushとbuildをしてください)
成功したらcircleCIのJOBSのタブの表示がこのようになります。(一番上の緑色がそうです。)
さて、ここまできたらようやく
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.8TerraformでAWSを扱うためのIAMユーザの設定
terraformでAWSを扱うにはIAMユーザのACCESS KEY、SECRET KEY(とDEFAULT REGION)が必要です。
ここだけはterraformの管理下におけないので、AWSのコンソールで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のリポジトリを作成します。秘匿情報を扱うので、プライベートリポジトリにすることに注意してください。
さて、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 .gitignoreterraform_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前半はここで終わり
目次 全体像を把握しよう Dockerを用いてRailsの環境を構築 CircleCIでCIの設定 Terraformを使うための準備 前半はここで終わり←今ココ 諸々の大体の準備が終わった時点で一旦区切ります!
後半からいよいよTerraformを使ってインフラを構築していきますので、気長にお待ちください。後半はコチラ(にする予定)
- 投稿日:2019-12-04T11:55:00+09:00
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超えてて驚いた
- 投稿日:2019-12-04T11:04:12+09:00
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/npxNode.jsコンテナからnodeとnpmのバイナリをコピーし、シンボリックリンクを貼るだけ!
簡単でお手軽で早いです!参考
Nuxt.js2.10とLaravel(6.0)を同一ディレクトリで動かすnuxt-laravelをDockerで環境構築
明日は@mei_9961さんの記事です。
- 投稿日:2019-12-04T10:33:35+09:00
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おじさん、ぜひ試してみてください。参考
- 投稿日:2019-12-04T10:27:22+09:00
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」
手順はこちらを参考
- 投稿日:2019-12-04T03:02:04+09:00
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-rustDocker 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-rustGitHub 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/mdbookhadolint job
hadolint: runs-on: macos-latest steps: - uses: actions/checkout@v1 with: fetch-depth: 1 - run: brew install hadolint - run: hadolint ./Dockerfilehadolint で
Dockerfile
をチェックしています。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-stdinGitHub 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 だけで完結させても問題ないでしょう。
以上です。ありがとうございました。