- 投稿日:2019-05-21T23:45:27+09:00
Docker を使う(docker build でオリジナルのイメージを作成する)
前回の記事 の続きです。
今回は、docker build
を使っていきたいと思います。
とは言っても、一から説明するととても大変なので、事例を紹介しつつ、かいつまんで説明しようと思います。何ができるのか?
要するに、オリジナルの docker イメージを作ります。
Docker Hub では、たくさんのイメージが公開されています。
当然それらを使うのですが、公開されている docker イメージをそのまま使っても、自分が書いたソースコードは docker イメージ内にないのでそのままでは動かせないわけです。そこで、自分のソースコードをあらかじめ埋め込んだイメージを作成し、そのイメージでコンテナを起動することによって、高速に、何度でも同じ環境を構築できる、ということですね。
docker build コマンド
ここでようやく、
docker build
コマンドが出てきます。
ただ、docker build
をするためには Dockerfile というファイルが必要になります。
この Dockerfile 内に、作成するイメージをどう構築するかを記述します。今回のテーマ
今回は、 CentOS のイメージに httpd をインストールして Web サーバーとして起動したいと思います。
本当はそんなことしないとは思いますが・・・
いいサンプルが思いつかず、こうなりました。で、今回用意するのは以下の3つです。
- Dockerfile
- entrypoint.sh
- index.html
順を追って説明します。
Dockerfile
docker build
を使う際に必須のものです。
ファイル名はこれじゃなくてもいいのですが、デフォルトがこの名称なのでこのままいきます。
詳しい説明は自分もあまり理解していないためできないので、参考書を購入し勉強しましょう・・・今回使用する Dockerfile の中身はこちら。
# ベースイメージの指定 FROM centos:centos7.5.1804 # ディレクトリ作成を実行 RUN mkdir /var/html > /dev/null 2>&1 # httpd のインストール RUN yum install -y httpd # ファイルのコピー COPY index.html /var/html COPY entrypoint.sh /usr/bin # docker のネットワーク上でコンテナのポートを開放 EXPOSE 8000 # docker run 時に絶対に実行されるコマンドを定義 ENTRYPOINT ["bin/bash", "/usr/bin/entrypoint.sh"] # docker run 時の引数でコマンドを省略した時に実行されるコマンドを定義 CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]なるべく色んなものを詰め込んでみました。
分かる範囲で説明します。FROM
まあ、書いてある通りです。
Docker Hubで公開されているイメージを基にしています。
常に最新のものを使用する場合は
FROM centos:latest
と書きますが、そんなことをする人はいないと思います。
ベースのバージョンが変わるとその後に実行するものが動かなくなる可能性がありますからね。RUN
ビルド時に実行されるものです。
ここが間違っていたりするとイメージは作成できません。
なお、上記の例では Shell 形式で記述しています。
この形式で記述すると、 /bin/sh で実行されるそうです。COPY
ファイルをコンテナ内に配置する際に使用します。
上記の書き方だと、docker build
を実行したカレントディレクトリに entrypoint.sh と index.html が存在している必要があります。EXPOSE
コンテナのポートを開放します。
このポートを介してコンテナと通信をします。
ここでは、 8000 番ポートを開けています。ENTRYPOINT
イメージ作成後、実際に
docker run
でコンテナを起動する際に実行されるコマンドです。
上記の例では、 Exec 形式で記述しています。
この形式では、実行したいコマンドを JSON 配列で指定します。
また、シェルを介さずに直接実行されるそうです。
そのため上記では /bin/bash を用いて entrypoint.sh を実行するように記載しています。CMD
イメージ作成後、実際に
docker run
でコンテナを起動する際に実行されるコマンドです。
ENTRYPOINT と異なる点は、こちらのコマンドはdocker run
時に引数でコマンドを指定すると、 CMD で指定したコマンドは実行されない点です。次は entrypoint.sh の説明です。
entrypoint.sh
Dockerfile 内で呼び出して実行しているシェルですが、中身は以下のように書いています。
#!/bin/bash set -e conf () { echo "ServerRoot \"/etc/httpd\"" echo "Listen 8000" echo "ServerName `hostname`" echo "Include conf.modules.d/*.conf" echo "User apache" echo "Group apache" echo "ServerAdmin root@localhost" echo "<Directory />" echo " AllowOverride none" echo " Require all denied" echo "</Directory>" echo "DocumentRoot \"/var/html\"" echo "<Directory \"/var/www\">" echo " AllowOverride None" echo " Require all granted" echo "</Directory>" echo "<Directory \"/var/html\">" echo " Options Indexes FollowSymLinks" echo " AllowOverride None" echo " Require all granted" echo "</Directory>" echo "<IfModule dir_module>" echo " DirectoryIndex index.html" echo "</IfModule>" echo "<Files \".ht*\">" echo " Require all denied" echo "</Files>" echo "ErrorLog \"logs/error_log\"" echo "LogLevel warn" echo "<IfModule log_config_module>" echo " LogFormat \"%h %l %u %t \\\"%r\\\" %>s %b \\\"%{Referer}i\\\" \\\"%{User-Agent}i\\\"\" combined" echo " LogFormat \"%h %l %u %t \\\"%r\\\" %>s %b\" common" echo " <IfModule logio_module>" echo " LogFormat \"%h %l %u %t \\\"%r\\\" %>s %b \\\"%{Referer}i\\\" \\\"%{User-Agent}i\\\" %I %O\" combinedio" echo " </IfModule>" echo " CustomLog \"logs/access_log\" combined" echo "</IfModule>" echo "<IfModule alias_module>" echo " ScriptAlias /cgi-bin/ \"/var/www/cgi-bin/\"" echo "</IfModule>" echo "<Directory \"/var/www/cgi-bin\">" echo " AllowOverride None" echo " Options None" echo " Require all granted" echo "</Directory>" echo "<IfModule mime_module>" echo " TypesConfig /etc/mime.types" echo " AddType application/x-compress .Z" echo " AddType application/x-gzip .gz .tgz" echo " AddType text/html .shtml" echo " AddOutputFilter INCLUDES .shtml" echo "</IfModule>" echo "AddDefaultCharset UTF-8" echo "<IfModule mime_magic_module>" echo " MIMEMagicFile conf/magic" echo "</IfModule>" echo "EnableSendfile on" echo "IncludeOptional conf.d/*.conf" } # 既存のコンフィグを書き換え conf > /etc/httpd/conf/httpd.conf exec "$@"別にシェルで書く必要はないのですが・・・
httpd.conf を書き換えるためのシェルです。
シェルでやることのメリットとしては、内部でhostname
のコマンド結果を使って動的に ServerName を変更できることですかね・・・
もっといいシチュエーションがあると思いますが、今回はこんなこともできるよ、という紹介です。
Listen
だけ、 8000 番ポートを指定するように変更しています。
これは、コンテナで開放するポートを 8000 番と指定しているので、合わせてあります。また、最後に
exec "$@"
と記載していますがこれがミソで、実際にビルドしたイメージを使ってdocker run
を実行した際、 Dockerfile に記載した通りだとすると以下のようなコマンドでコンテナが起動します。bin/bash /usr/bin/entrypoint.sh /usr/sbin/httpd -D FOREGROUNDそう、 ENTRYPOINT と CMD はどちらも実行されるので、横並びに実行されてしまいます。
そのため、 entrypoint.sh の引数として/usr/sbin/httpd -D FOREGROUND
を受け取り、これをシェルの中で実行するような形としています。
他にもやりようはあるのかもしれませんが、なぜかこのように書いています。
一般的ではないのかも。。。
あくまで ENTRYPOINT を使う例として無理矢理シェルを実行しているための処置だと思ってください。index.html
こちらはただ自前で用意しただけの HTML です。
entrypoint.sh で DocumentRoot を変更しているので、そこに合わせて配置しているだけにすぎません。
今回の例では、中身は以下のようにしてみました。<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <h1>こんにちは、世界!</h1> </body> </html>ビルドしてみる
ではでは、3つのファイルを用意したので実際にビルドしてみましょう。
/var/docker/centos
配下にファイルを配置しているものとして説明します。
こんな感じ。[root@localhost centos]# pwd /var/docker/centos [root@localhost centos]# ls Dockerfile entrypoint.sh index.htmlコマンドを実行します。
[root@localhost centos]# docker build -t testcent .
-t
は、名前とタグを決めます。
書式はname:tag
となります。今回はtestcent
という名前にしました。
タグは指定していないので、latest
に勝手になります。
最後のドット.
は、カレントディレクトリにある Dockerfile を使うときにこうなります。
Dockerfile の場所が異なる場合は、そのディレクトリを指定します。
なお、ファイル名が Dockerfile ではない場合は、-f
オプションを使って指定すればよいはずです。
(試してはいないです)上手くできれば、最後に
Successfully
うんちゃらと出ているはず。
ここで、docker images
を実行してみます。[root@localhost centos]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE testcent latest f7e4a3bceea6 3 minutes ago 326MB centos centos7.5.1804 cf49811e3cdb 2 months ago 200MBベースとなった centos の docker イメージと、自分でビルドした testcent があることが分かります。
ビルドしたイメージでコンテナを起動してみる
以下のようにコマンドを実行します。
[root@localhost centos]# docker run -d -p 80:8000 testcent
-d
オプションは、バックグランドでコンテナを実行し続けます。
-p {ホストのポート}:{コンテナのポート}
と指定することで、ホストのポートへのアクセスをコンテナのポートに飛ばすことができます。
Dockerfile でコンテナに 8000 番ポートをあけるように記載しているので、ホストの 80 番ポートをコンテナの 8000 番ポートに繋げました。コンテナの状態を確認してみます。
[root@localhost centos]# docker ps --no-trunc CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2570dda5f952162c34eb397ff8f934c23cb0ebf7b603dde166bad54150c6b22d testcent "bin/bash /usr/bin/entrypoint.sh /usr/sbin/httpd -D FOREGROUND" 4 minutes ago Up 4 minutes 0.0.0.0:80->8000/tcp cranky_blackwell
--no-trunc
オプションで省略せずに表示させているため横長ですが・・・
Dockerfile で指定したENTRYPOINT
とCMD
が実行されていますね。
この状態で、localhost:80
へアクセスしてみましょう。[root@localhost centos]# curl http://localhost:80 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <h1>こんにちは、世界!</h1> </body> </html>ホストの 80 番ポートはコンテナの 8000 番ポートへ飛び、8000 番ポートで httpd が Listen しているので、 index.html を表示することができました。
最後に
長い間下書き状態で放置していたので、端折ってしまっている部分があるかも・・・
また、 httpd が分からないと難しい部分もあったかもしれません。
正直なところ、自分で LAMP 環境を構築できる程度の知識は必要です。
ちゃんと学ぼうと思ったら、 Virtualbox に CentOS の ISO イメージファイルを使って一からインストールをしてみたり yum install で httpd を入れてみたり、 iptables やら SELinux やら、 systemctl やら firewalld やら、ifconfig やら ip addr show やら、よく使うような基本的なところは勉強しておきたいですね。
自分もそんなにやってませんが・・・(´・ω・`)次回も記事を書こうかと思いますが、今度は Docker Compose か Docker Swarm あたりにしようかと思います。
もう次からは超絶難しくなるので、 Docker Compose はすっ飛ばして Docker Swarm にするかも。
Kubernetes は・・・職場で使ってないので使ったことがなく・・・
Docker Swarm の記事が書けたら、 Kubernetes を勉強してみたいなぁと思ってます。
- 投稿日:2019-05-21T22:01:57+09:00
Android開発者のためのOCR入門(tess-two編)
はじめに
AndroidアプリでOCRを実装する際、TessaractのAndroid用ラッパーライブラリtess-twoを利用するか、またはML Kit for Firebaseを利用する方法があります。
今回は、前者のtess-twoを使ってギャラリーから画像を選択してその画像にある文字を読み取るアプリを作ってみたいと思います。サンプルコードはここに置いてあります。
インストール
まずはtess-twoを入れます。また、画像をギャラリーから読み込む際にExifを読み取って画像の向きを正しい方向に直したいのでexifinterfaceも入れておきます。
app/build.gradle.kts// 略 dependencies { implementation("com.rmtheis:tess-two:9.0.0") implementation("androidx.exifinterface:exifinterface:1.0.0") }続いてappディレクトリ以下に/assets/tessdata/ディレクトリを作成して言語データを配置します。
今回はこんな感じにしました。app - assets - tessdata |-- jpn.traineddata `-- eng.traineddata言語データは下記からダウンロードしてください。tess-twoはtesseract3.0.5以下で作成された言語データが必要です。
https://github.com/tesseract-ocr/tessdata/tree/3.04.00実装
tess-twoの使い方自体は非常に簡単です。わずか5行程度です。
OCRUtil.ktval baseApi = TessBaseAPI() // initで言語データを読み込む baseApi.init(context.getFilesDir().toString(), "jpn") // ギャラリーから読み込んだ画像をFile or Bitmap or byte[] or Pix形式に変換して渡してあげる baseApi.setImage(bitmap) // これだけで読み取ったテキストを取得できる val recognizedText = baseApi.utF8Text baseApi.end()ですが注意点が2つあります。
1つ目はbaseApi.initはの中では、言語データをFileでopenしているためFileで読み込める場所にコピーしてあげる必要があります。
OCRUtil.ktprivate val TESS_DATA_DIR = "tessdata" + File.separator private val TESS_TRAINED_DATA = arrayListOf("eng.traineddata", "jpn.traineddata") private fun copyFiles(context: Context) { try { TESS_TRAINED_DATA.forEach { val filePath = context.filesDir.toString() + File.separator + TESS_DATA_DIR + it // assets以下をinputStreamでopenしてbaseApi.initで読み込める領域にコピー context.assets.open(TESS_DATA_DIR + it).use {inputStream -> FileOutputStream(filePath).use {outStream -> val buffer = ByteArray(1024) var read = inputStream.read(buffer) while (read != -1) { outStream.write(buffer, 0, read) read = inputStream.read(buffer) } outStream.flush() } } val file = File(filePath) if (!file.exists()) throw FileNotFoundException() } } catch (e: FileNotFoundException) { e.printStackTrace() } catch (e: IOException) { e.printStackTrace() } }2つ目はtess-twoで画像を読み込む場合、画像の向きが正しくないと当然読み込めません。ギャラリーから読み込む場合、画像が回転してしまっている場合があるのでExifを見て正しい方向に直す必要があります。
MainActivity.kt// 画像のuri uri?.let { contentResolver.openFileDescriptor(it, "r").use {parcelFileDescriptorNullable -> parcelFileDescriptorNullable?.let {parcelFileDescriptor -> val fileDescriptor = parcelFileDescriptor.fileDescriptor bitmapOrigin = BitmapFactory.decodeFileDescriptor(fileDescriptor) contentResolver.openInputStream(it).use { it?.let { // ExifInterfaceを初期化 val exifInterface = ExifInterface(it) val orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED) val degrees = when (orientation) { // 正しい方向の場合は回転させない 1 -> { 0f } // 逆向きなので180度回転させる 3-> { 180f } // 左向きの画像になってるので90度回転させる 6 -> { 90f } // 右向きの画像になってるので270度回転させる 8 -> { 270f } else -> { 0f } } val matrix = Matrix() val imageWidth = bitmapOrigin?.getWidth() ?: 0 val imageHeight = bitmapOrigin?.getHeight() ?: 0 matrix.setRotate(degrees, imageWidth.toFloat() / 2, imageHeight.toFloat() / 2) bitmap = Bitmap.createBitmap(bitmapOrigin!!, 0, 0, imageWidth, imageHeight, matrix, true) } } } } }ここまで実装したら早速動かしてみましょう。今回はこちらの画像から文字を読み取ってみます。
早速アプリを起動して画像を読み込んでみると…
無事、文字を読み取ることが出来るようになりました!が、、精度がいまいちです…
日本語言語データをカスタマイズ
先程の画像を見てみると数字が全然読み取れていないことに気がつくと思います。今回はこの数字の精度を改善してみたいと思います。
数字の誤変換の原因ですが、こちらのサイトを見てみると認識結果の変換マッピングが原因のようです。なのでjpn.unicharambigsを修正してみます。
今回、tesseract-ocrの環境をDockerで用意しました。こちらの環境を使ってカスタマイズします。
https://github.com/tarumzu/OCRSampleAndroid/tree/master/containersまずは下記の手順でDockerコンテナにログイン
cd containers docker-compose build docker-compose up docker-compose exec tesseract bashログインしたら、jpn.traineddataをダウンロード、次にcombine_tessdataコマンドのeオプションでjpn.traineddataからjpn.unicharambigsを取り出します。
wget https://github.com/tesseract-ocr/tessdata/raw/3.04.00/jpn.traineddata combine_tessdata -e jpn.traineddata jpn.unicharambigs
取り出したらjpn.unicharambigsをvimで開いて下記の行を削除します。これは左側のキャラクタを右側のものに変換するというルールなのですが正直必要ない変換だと思います。
1 l 1 ー 1 1 | 1 ー 1 1 I 1 ー 1 1 1 1 ー 1 1 | 1 ー 1 1 O 1 。 1 1 ° 1 。 1ここまでできたらjpn.traineddataを再作成します。
combine_tessdata -o jpn.traineddata jpn.unicharambigs
では、先程作成した言語データはcontainersに保存されます。この言語データを使って再度画像を読み込んでみましょう。
たったこれだけで数字の精度が劇的に改善できました!
終わりに
簡単にOCRが実装できましたが言語データのカスタマイズは必須ですね…
次はML Kit for FirebaseでOCRを実装してみて違いなどを記事にしてみたいと思います。参考
- 投稿日:2019-05-21T20:57:53+09:00
Dockerfileの作成の留意点備忘録
- 投稿日:2019-05-21T20:30:29+09:00
まじか! docker の volume にパスの長さが影響するなんて (gitlab)
結論
- gitlab を docker で立ち上げる時、
--volume
のパスの長さが、長過ぎる時は気をつけろ。正常起動
- 正常起動とエラーとの違いは、 volume のパス。
- 正常起動:
${HOME}/Downloads
- エラー:
${PWD}
- HOME と PWD を展開した時の長さが全然違う。(今回の場合の桁数)
- 正常起動(HOME): 14 バイト
- エラー(PWD): 83 バイト
docker run --detach \ --hostname localhost \ --publish 10080:80 \ --publish 10022:22 \ --name gitlab \ --restart always \ --volume ${HOME}/Downloads/gitlab/config:/etc/gitlab \ --volume ${HOME}/Downloads/gitlab/logs:/var/log/gitlab \ --volume ${HOME}/Downloads/gitlab/data:/var/opt/gitlab \ gitlab/gitlab-ce:latestエラー
docker run --detach \ --hostname localhost \ --publish 10080:80 \ --publish 10022:22 \ --name gitlab \ --restart always \ --volume ${PWD}/gitlab/config:/etc/gitlab \ --volume ${PWD}/gitlab/logs:/var/log/gitlab \ --volume ${PWD}/gitlab/data:/var/opt/gitlab \ gitlab/gitlab-ce:latest
- エラー内容
Error executing action `run` on resource 'bash[migrate gitlab-rails database]'There was an error running gitlab-ctl reconfigure: bash[migrate gitlab-rails database] (gitlab::database_migrations line 51) had an error: Mixlib::ShellOut::ShellCommandFailed: Expected process to exit with [0], but received '1' ---- Begin output of "bash" "/tmp/chef-script20190521-24-51lpum" ---- STDOUT: rake aborted! PG::ConnectionBad: could not connect to server: No such file or directory Is the server running locally and accepting connections on Unix domain socket "/var/opt/gitlab/postgresql/.s.PGSQL.5432"? /opt/gitlab/embedded/service/gitlab-rails/lib/tasks/gitlab/db.rake:55:in `block (3 levels) in <top (required)>' /opt/gitlab/embedded/bin/bundle:23:in `load' /opt/gitlab/embedded/bin/bundle:23:in `<main>' Tasks: TOP => gitlab:db:configure (See full trace by running task with --trace) STDERR: ---- End output of "bash" "/tmp/chef-script20190521-24-51lpum" ---- Ran "bash" "/tmp/chef-script20190521-24-51lpum" returned 1
- 投稿日:2019-05-21T16:55:58+09:00
Dockerはコンテナ内プログラムの終了ステータスもホスト側に渡してくれるよというお話
タイトルの通りです。実際に確認してみましょう。
Pythonのコンテナを立ち上げて、終了ステータスを114
に設定して終了させてみます。$ docker run -it python # pythonコンテナを起動 Python 3.7.3 (default, May 8 2019, 05:28:42) [GCC 6.3.0 20170516] on linux Type "help", "copyright", "credits" or "license" for more information. >>> exit(114) # 終了ステータスを114に設定してPythonを終了させる $ echo $? # 終了ステータスを見てみる 114無事にホスト側で終了させるを確認してみると、
114
となっていますね。
- 投稿日:2019-05-21T12:31:57+09:00
フロントエンドでもtrivyを使って脆弱性対策したい!
伝えたいこと
- フロントエンドもコンテナ経由でアクセスさせよう
- コンテナならtrivyを使ってヤバそうな脆弱性を知ることができる
- trivyならCIに組み込むことも簡単
- multi-stage buildの場合は工夫が必要
- いまの状況でできる範囲の脆弱性対策からはじめよう
今回の記事でできること
CI上で、本番用のフロントエンドのコンテナをmulti-satge buildでつくり、
trivyを利用して脆弱性を検知するところまでを目指します。最後に、trivyを利用したレベル別の運用イメージも提案します。
導入
週末、trivyというコンテナ向けの脆弱性検知ツールが正式リリースされました。公開が5日目の5/21 12:00時点で 900star以上獲得しています。
trivyの詳細は原作者である @knqyf263 さんの「CIで使えるコンテナの脆弱性スキャナ」という記事を参照ください。
ただフロントエンドの方は「脆弱性はインフラ側が対応してくれるから、自分には関係ない」と思っていないでしょうか。
そのような方をメインに、今回は以下の内容を紹介します。
- コンテナを使ってフロントエンドの本番環境を構築する
- trivyを利用して、脆弱性が含まれていないかチェックする
- trivyを通じた脆弱性の運用方法を学ぶ
僕は普段はフロントエンドも担当するエンジニアで、セキュリティに対して体系だって学んだことはありません。ただ、trivyのコミッタでもあり、脆弱性の運用も少し知っているので、フロントエンドでもDevSecOpsを定着させるきっかけになるといいなと考えています。
ソースコード
今回使うコードは、すべて以下のレポジトリにあります。
https://github.com/tomoyamachi/trivy-with-react
この記事では触れませんが、開発環境でもコンテナ経由でホットリロードするような仕組みになっています。
実践 : プロジェクト作成~trivyの導入
ここからはプロジェクトの作成と、CIにtrivyを組み込むまでの手順を書きます
1. プロジェクトの作成
今回はCreate React Appを利用して、Reactのプロジェクトをまず作成します。
そして、デフォルトではパッケージ管理ツールにyarnが利用されているので、今回はプロジェクトのパッケージ管理をnpmで実行します。$ npx create-react-app trivy-with-react $ cd trivy-with-react $ rm -fr node_modules yarn.lock $ npm install2. 本番用コンテナの作成
- 本番用のコードを生成
- nginxコンテナに生成したコードを移し、nginxを起動
という手順をとります。
通常であれば1ファイル
そして通常2つのステップは、multi-stage buildを利用して、1つのDockerfileでコンテナを作成します。
Dockerfile# build environment FROM node:12.2.0-alpine as build WORKDIR /app ENV PATH /app/node_modules/.bin:$PATH COPY package.json /app/package.json RUN npm install --silent RUN npm install react-scripts@3.0.1 -g --silent COPY . /app RUN npm run build # production environment FROM nginx:1.16.0-alpine COPY --from=build /app/build /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]1ファイルでmulti-stage buildの問題点
multi-stage buildは1つのDockerfileで最終成果物のimageだけを出力してくれるので、通常はベストプラクティスです。
その場合、以下のように、builder側のイメージがIMAGE IDでしか特定できない形で作成されているのがわかります。$ docker images REPOSITORY TAG IMAGE ID ... trivy-with-react test 96c4fdfac287 # nginx側 <none> <none> 41fd97016d25 # builder側しかし、今回、脆弱性はbuilder側のコンテナにもあることに気がついた方もいるでしょう。package-lock.json もそうですし、一般的ではないですが、Static Linkでビルドする場合も考えられます。
trivyは
REPOSITORY:TAG
の形式でコンテナイメージを指定してスキャンするので、multi-stage buildにしてしまうと、builder側の脆弱性が放置されてしまいます。そのため、builder側と、nginx側のコンテナイメージを独立させて、参照可能にしたいと思います。具体的には、Dockerfileを分けることで、それぞれのイメージにタグを付けます。
また、2回コマンド実行するのも微妙なのでmakefileを作成します。※ もしも1つのDockerfileでmulti-stage buildをし、builder側のイメージ名を指定することができるのであれば、切実に情報がほしいです!
trivy用の構成
コードを見てもらえばわかりますが、Dockerfileをbuilder用(Dockerfile.builder)と、nginx用(Dockerfile.prod)に分けて、makefileは以下のようにしています。
Dockerfile.builderFROM node:12.2.0-alpine as builder WORKDIR /app ENV PATH /app/node_modules/.bin:$PATH COPY package.json /app/package.json RUN npm install --silent RUN npm install react-scripts@3.0.1 -g --silent COPY . /app RUN npm run buildDockerfile.prodARG tag FROM local-builder:${tag} as builder FROM nginx:1.16.0-alpine ARG tag COPY --from=builder /app/build /usr/share/nginx/html RUN rm /etc/nginx/conf.d/default.conf COPY nginx/nginx.conf /etc/nginx/conf.d EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]makefile.PHONY: build all TAG := $(shell git rev-parse HEAD) all: build build: docker build . -f Dockerfile.builder -t local-builder:$(TAG) docker build . --build-arg tag=$(TAG) -f Dockerfile.prod -t built:$(TAG)このため、以下のようにデフォルトではコミットハッシュごとにコンテナを作成し、変数TAGが指定されていればそれを利用します。
$ make build # 最新のコミットハッシュをコンテナのタグに利用 $ make build TAG=hoge # hogeをコンテナのタグに指定実際のレポジトリでは、SPA用にnginx.confを置いたりしているので、必要な方はご確認ください。
1つのDockerfileで、同様のことができる方法をご存知の方いたら教えてください...
ビルドしたコンテナのテスト
では、さっそくビルドしたコンテナにアクセスしてみましょう。
$ make build TAG=test $ docker run -p 8000:80 built:test以上を実行し、 http://localhost:8000 を開くと、画面が表示されることが確認できました。
ついでに、nginxでSPA対応できているかを確認するため、 http://localhost:8000/spa/test が開けることを確認します。
3. trivyでスキャン
では、builder側とnginx側をスキャンしましょう。
trivyはインストールしてある前提ですが、makefileに以下のコードを追加します。buildと同じようにTAGを指定することで、指定したイメージをスキャンできるようになります。
ci-scan: trivy --exit-code 1 --severity HIGH,CRITICAL --quiet --auto-refresh local-builder:$(TAG) trivy --exit-code 1 --quiet --auto-refresh built:$(TAG)
--exit-code
オプションで脆弱性を検知した場合の終了コードを指定できるので、スキャンはしたいけど、CIは止めたくない人は--exit-code 0
(デフォルトなので省略可) を利用しましょう。4. CircleCIで実行
CircleCIの場合は以下のようにします。
trivyの脆弱性データの取得が重いので、脆弱性データなどはキャッシュに保存すると2回目以降のスキャンが高速になります。version: 2 jobs: build: docker: - image: docker:18.09-git steps: - checkout - setup_remote_docker - restore_cache: key: vulnerability-db - run: name: Build image command: | apk add --update --no-cache make curl make build TAG=${CIRCLE_SHA1} - run: name: Install latest trivy command: | VERSION=$( curl -I https://github.com/knqyf263/trivy/releases/latest | \ grep -o '/tag/v[0-9]\+.[0-9]\+\.[0-9]\+' | \ sed -E 's:/tag/v([0-9\.]+):\1:' ) wget https://github.com/knqyf263/trivy/releases/download/v${VERSION}/trivy_${VERSION}_Linux-64bit.tar.gz tar zxvf trivy_${VERSION}_Linux-64bit.tar.gz mv trivy /usr/local/bin - run: name: Scan local images with trivy command: | make ci-scan TAG=${CIRCLE_SHA1} - save_cache: key: vulnerability-db paths: - $HOME/.cache/trivy workflows: version: 2 release: jobs: - build正常終了の場合、以下のようになります。
builder側とnginx側のどちらにも脆弱性がなかったということがわかりました。脆弱性があった場合CIを止めたい
脆弱性が見つかった場合に、CIを止めたい場合は
--exit-code 1
オプションを利用します。このオプションにより、脆弱性が見つかった場合、異常終了(終了コードが1)になります。
また、severity
オプションを利用することで、検知したい脆弱性の危険度を指定できます。trivy --exit-code 1 --severity HIGH,CRITICAL \ --quiet --auto-refresh \ local-builder:$(TAG)この状態で脆弱性のあるパッケージを追加して結果を見ると、CIが失敗することが確認できました!
https://circleci.com/gh/tomoyamachi/trivy-with-react/8trivyを使った運用スタイル
さて、では脆弱性が見つかった場合、どのようにすればよいでしょうか?
こうしたほうがいい、というのはありますが、それぞれの運用方針にそって決めればいいと思います。
以下に、「とりあえず導入」「ちょっと気になる」「がんばる」のコースとそれぞれによさそうな運用方針をまとめました。
とりあえず導入 ちょっと気になる がんばる 検知時のCI とめない(--exit-code 0) とめる(--exit-code 1) とめる(--exit-code 1) 対応方針 対応せずSlackなどに通知 バージョンアップして、駄目なら無視する (.trivyignore) 脆弱性を調べて、必要なら対応 検知タイミング ビルド時 ビルド時 ビルド時&定期実行 全く興味なくても、導入するだけ導入したい、という人がいてもいいと思うので、できる範囲ですすめられるといいかなと思います。
以下は、「とりあえず導入」以上の興味がある人に対しての方法論になります。
1. 脆弱性を調べ、本当にCRITICALかをチェック
まず、その脆弱性が自分のアプリケーションにとって本当に問題になるのかを理解しましょう。
NVDなどで脆弱性の内容を調べる方法もあるし、まわりの詳しい人に聞くのもありです。
自分で調べる場合、脆弱性が危険かどうかの判断は このスライドの「トリアージ」以降の章が役立つかと思います。2. 重要ではない場合は、その脆弱性を無視する
プロジェクトに
.trivyignore
を作成し、無視したい脆弱性ID(以下、VULNERABILITY ID
)を記載すると、そのIDを無視してくれます。
さきほどの例でいうと、以下のように指定すれば、それ以降同じVULNERABILITY ID
は検知されなくなります。.trivyignoreNSWG-ECO-328 CVE-2019-5428 CVE-2019-11358この対応で先程停止していたワークフローがとおるようになったのがわかります。
https://circleci.com/gh/tomoyamachi/trivy-with-react/103. 重要な場合は...
重要な場合、バージョンアップをすれば良いと思うのですが、そうではない場合、自分で修正したコードを利用する方法などもあります。
アプリケーションパッケージの場合
たとえば、npmのパッケージであれば、 この記事の方法で でforkした自分のレポジトリを入れることが可能です。
ただし、その場合、バージョン情報が以下のようになり、そのパッケージの脆弱性チェックができなくなる点に留意ください。
"jquery": { "version": "git+https://<token>:x-oauth-basic@github.com/tomoyamachi/jquery.git#438b1a3e8a52d3e4efd8aba45498477038849c97", "from": "git+https://<token>:x-oauth-basic@github.com/tomoyamachi/jquery.git" },OSのパッケージの場合
検出されるパッケージバージョン自体は変わらないので、patchfileを当てるコマンドを実行するのと同時に、
.trivyignore
に対象のVULNERABILITY ID
を追加してください。まとめ
今回は、以下のことを書きました。
- フロントエンドをコンテナで運用する
- trivyで脆弱性対策できる
- 脆弱性のおおまかな運用方法
なお、コンテナになっていない場合は、Vulsという素晴らしい脆弱性スキャナがあるので、こちらをご利用ください。動作中のコンテナもスキャン可能です。
すこし導入に準備が必要ですが、あなたのサーバは本当に安全ですか?今もっともイケてる脆弱性検知ツールVulsを使ってみた を参考にお試しください。謝辞
記事を書く際に、 @knqyf263 さん、 @sadayuki-matsuno さん、 @codehex さんにご意見をいただきました。ありがとうございました!!
以上です。
- 投稿日:2019-05-21T12:31:57+09:00
フロントエンドでもtrivyを使って脆弱性対策したい!
伝えたいこと
- フロントエンドもコンテナ経由でアクセスさせよう
- コンテナならtrivyを使ってヤバそうな脆弱性を知ることができる
- trivyならCIに組み込むことも簡単
- multi-stage buildの場合は工夫が必要
- いまの状況でできる範囲の脆弱性対策からはじめよう
今回の記事でできること
CI上で、本番用のフロントエンドのコンテナをmulti-satge buildでつくり、
trivyを利用して脆弱性を検知するところまでを目指します。最後に、trivyを利用したレベル別の運用イメージも提案します。
導入
週末、trivyというコンテナ向けの脆弱性検知ツールが正式リリースされました。公開が5日目の5/21 12:00時点で 900star以上獲得しています。
trivyの詳細は原作者である @knqyf263 さんの「CIで使えるコンテナの脆弱性スキャナ」という記事を参照ください。
今回は、フロントエンドをメインに、今回は以下の内容を紹介します。
- コンテナを使ってフロントエンドの本番環境を構築する
- trivyを利用して、脆弱性が含まれていないかチェックする
- trivyを通じた脆弱性の運用方法を学ぶ
僕も、普段はフロントエンドも担当するエンジニアで、セキュリティに対して体系だって学んだことはありません。ただ、trivyのコミッタでもあり、脆弱性の運用も少し知っているので、フロントエンドでもDevSecOpsを定着させるきっかけになるといいなと考えています。
ソースコード
今回使うコードは、すべて以下のレポジトリにあります。
https://github.com/tomoyamachi/trivy-with-react
この記事では触れませんが、開発環境でもコンテナ経由でホットリロードするような仕組みになっています。
実践 : プロジェクト作成~trivyの導入
ここからはプロジェクトの作成と、CIにtrivyを組み込むまでの手順を書きます
1. プロジェクトの作成
今回はCreate React Appを利用して、Reactのプロジェクトをまず作成します。
そして、デフォルトではパッケージ管理ツールにyarnが利用されているので、今回はプロジェクトのパッケージ管理をnpmで実行します。$ npx create-react-app trivy-with-react $ cd trivy-with-react $ rm -fr node_modules yarn.lock $ npm install2. 本番用コンテナの作成
- 本番用のコードを生成
- nginxコンテナに生成したコードを移し、nginxを起動
という手順をとります。
通常であれば1ファイル
そして通常2つのステップは、multi-stage buildを利用して、1つのDockerfileでコンテナを作成します。
Dockerfile# build environment FROM node:12.2.0-alpine as build WORKDIR /app ENV PATH /app/node_modules/.bin:$PATH COPY package.json /app/package.json RUN npm install --silent RUN npm install react-scripts@3.0.1 -g --silent COPY . /app RUN npm run build # production environment FROM nginx:1.16.0-alpine COPY --from=build /app/build /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]1ファイルでmulti-stage buildの問題点
multi-stage buildは1つのDockerfileで最終成果物のimageだけを出力してくれるので、通常はベストプラクティスです。
その場合、以下のように、builder側のイメージがIMAGE IDでしか特定できない形で作成されているのがわかります。$ docker images REPOSITORY TAG IMAGE ID ... trivy-with-react test 96c4fdfac287 # nginx側 <none> <none> 41fd97016d25 # builder側しかし、今回、脆弱性はbuilder側のコンテナにもあることに気がついた方もいるでしょう。package-lock.json もそうですし、一般的ではないですが、Static Linkでビルドする場合も考えられます。
trivyは
REPOSITORY:TAG
の形式でコンテナイメージを指定してスキャンするので、multi-stage buildにしてしまうと、builder側の脆弱性が放置されてしまいます。そのため、builder側と、nginx側のコンテナイメージを独立させて、参照可能にしたいと思います。具体的には、Dockerfileを分けることで、それぞれのイメージにタグを付けます。
また、2回コマンド実行するのも微妙なのでmakefileを作成します。※ もしも1つのDockerfileでmulti-stage buildをし、builder側のイメージ名を指定することができるのであれば、切実に情報がほしいです!
trivy用の構成
コードを見てもらえばわかりますが、Dockerfileをbuilder用(Dockerfile.builder)と、nginx用(Dockerfile.prod)に分けて、makefileは以下のようにしています。
Dockerfile.builderFROM node:12.2.0-alpine as builder WORKDIR /app ENV PATH /app/node_modules/.bin:$PATH COPY package.json /app/package.json RUN npm install --silent RUN npm install react-scripts@3.0.1 -g --silent COPY . /app RUN npm run buildDockerfile.prodARG tag FROM local-builder:${tag} as builder FROM nginx:1.16.0-alpine ARG tag COPY --from=builder /app/build /usr/share/nginx/html RUN rm /etc/nginx/conf.d/default.conf COPY nginx/nginx.conf /etc/nginx/conf.d EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]makefile.PHONY: build all TAG := $(shell git rev-parse HEAD) all: build build: docker build . -f Dockerfile.builder -t local-builder:$(TAG) docker build . --build-arg tag=$(TAG) -f Dockerfile.prod -t built:$(TAG)このため、以下のようにデフォルトではコミットハッシュごとにコンテナを作成し、変数TAGが指定されていればそれを利用します。
$ make build # 最新のコミットハッシュをコンテナのタグに利用 $ make build TAG=hoge # hogeをコンテナのタグに指定実際のレポジトリでは、SPA用にnginx.confを置いたりしているので、必要な方はご確認ください。
1つのDockerfileで、同様のことができる方法をご存知の方いたら教えてください...
ビルドしたコンテナのテスト
では、さっそくビルドしたコンテナにアクセスしてみましょう。
$ make build TAG=test $ docker run -p 8000:80 built:test以上を実行し、 http://localhost:8000 を開くと、画面が表示されることが確認できました。
ついでに、nginxでSPA対応できているかを確認するため、 http://localhost:8000/spa/test が開けることを確認します。
3. trivyでスキャン
では、builder側とnginx側をスキャンしましょう。
trivyはインストールしてある前提ですが、makefileに以下のコードを追加します。buildと同じようにTAGを指定することで、指定したイメージをスキャンできるようになります。
ci-scan: trivy --exit-code 1 --severity HIGH,CRITICAL --quiet --auto-refresh local-builder:$(TAG) trivy --exit-code 1 --quiet --auto-refresh built:$(TAG)
--exit-code
オプションで脆弱性を検知した場合の終了コードを指定できるので、スキャンはしたいけど、CIは止めたくない人は--exit-code 0
(デフォルトなので省略可) を利用しましょう。4. CircleCIで実行
CircleCIの場合は以下のようにします。
trivyの脆弱性データの取得が重いので、脆弱性データなどはキャッシュに保存すると2回目以降のスキャンが高速になります。version: 2 jobs: build: docker: - image: docker:18.09-git steps: - checkout - setup_remote_docker - restore_cache: key: vulnerability-db - run: name: Build image command: | apk add --update --no-cache make curl make build TAG=${CIRCLE_SHA1} - run: name: Install latest trivy command: | VERSION=$( curl -I https://github.com/knqyf263/trivy/releases/latest | \ grep -o '/tag/v[0-9]\+.[0-9]\+\.[0-9]\+' | \ sed -E 's:/tag/v([0-9\.]+):\1:' ) wget https://github.com/knqyf263/trivy/releases/download/v${VERSION}/trivy_${VERSION}_Linux-64bit.tar.gz tar zxvf trivy_${VERSION}_Linux-64bit.tar.gz mv trivy /usr/local/bin - run: name: Scan local images with trivy command: | make ci-scan TAG=${CIRCLE_SHA1} - save_cache: key: vulnerability-db paths: - $HOME/.cache/trivy workflows: version: 2 release: jobs: - build正常終了の場合、以下のようになります。
builder側とnginx側のどちらにも脆弱性がなかったということがわかりました。脆弱性があった場合CIを止めたい
脆弱性が見つかった場合に、CIを止めたい場合は
--exit-code 1
オプションを利用します。このオプションにより、脆弱性が見つかった場合、異常終了(終了コードが1)になります。
また、severity
オプションを利用することで、検知したい脆弱性の危険度を指定できます。trivy --exit-code 1 --severity HIGH,CRITICAL \ --quiet --auto-refresh \ local-builder:$(TAG)この状態で脆弱性のあるパッケージを追加して結果を見ると、CIが失敗することが確認できました!
https://circleci.com/gh/tomoyamachi/trivy-with-react/8trivyを使った運用スタイル
さて、では脆弱性が見つかった場合、どのようにすればよいでしょうか?
こうしたほうがいい、というのはありますが、それぞれの運用方針にそって決めればいいと思います。
以下に、「とりあえず導入」「ちょっと気になる」「がんばる」のコースとそれぞれによさそうな運用方針をまとめました。
とりあえず導入 ちょっと気になる がんばる 検知時のCI とめない(--exit-code 0) とめる(--exit-code 1) とめる(--exit-code 1) 対応方針 対応せずSlackなどに通知 バージョンアップして、駄目なら無視する (.trivyignore) 脆弱性を調べて、必要なら対応 検知タイミング ビルド時 ビルド時 ビルド時&定期実行 全く興味なくても、導入するだけ導入したい、という人がいてもいいと思うので、できる範囲ですすめられるといいかなと思います。
以下は、「とりあえず導入」以上の興味がある人に対しての方法論になります。
1. 脆弱性を調べ、本当にCRITICALかをチェック
まず、その脆弱性が自分のアプリケーションにとって本当に問題になるのかを理解しましょう。
NVDなどで脆弱性の内容を調べる方法もあるし、まわりの詳しい人に聞くのもありです。
自分で調べる場合、脆弱性が危険かどうかの判断は このスライドの「トリアージ」以降の章が役立つかと思います。2. 重要ではない場合は、その脆弱性を無視する
プロジェクトに
.trivyignore
を作成し、無視したい脆弱性ID(以下、VULNERABILITY ID
)を記載すると、そのIDを無視してくれます。
さきほどの例でいうと、以下のように指定すれば、それ以降同じVULNERABILITY ID
は検知されなくなります。.trivyignoreNSWG-ECO-328 CVE-2019-5428 CVE-2019-11358この対応で先程停止していたワークフローがとおるようになったのがわかります。
https://circleci.com/gh/tomoyamachi/trivy-with-react/103. 重要な場合は...
重要な場合、バージョンアップをすれば良いと思うのですが、そうではない場合、自分で修正したコードを利用する方法などもあります。
アプリケーションパッケージの場合
たとえば、npmのパッケージであれば、 この記事の方法で でforkした自分のレポジトリを入れることが可能です。
ただし、その場合、バージョン情報が以下のようになり、そのパッケージの脆弱性チェックができなくなる点に留意ください。
"jquery": { "version": "git+https://<token>:x-oauth-basic@github.com/tomoyamachi/jquery.git#438b1a3e8a52d3e4efd8aba45498477038849c97", "from": "git+https://<token>:x-oauth-basic@github.com/tomoyamachi/jquery.git" },OSのパッケージの場合
検出されるパッケージバージョン自体は変わらないので、patchfileを当てるコマンドを実行するのと同時に、
.trivyignore
に対象のVULNERABILITY ID
を追加してください。まとめ
今回は、以下のことを書きました。
- フロントエンドをコンテナで運用する
- trivyで脆弱性対策できる
- 脆弱性のおおまかな運用方法
ところで、 記事を書いているときにnpmを使うのであれば、
npm audit
で簡単に脆弱性を取得できることを知りました… が、環境側の脆弱性もチェックできるので、コンテナ化しているのであれば、trivyを使ってみてください。
S3などにホストしている場合は、npm audit
だけでよさそうです。なお、コンテナになっていない場合で実行環境をチェックしたい場合、Vulsという素晴らしい脆弱性スキャナがあるので、こちらをご利用ください。動作中のコンテナもスキャン可能です。
すこし導入に準備が必要ですが、あなたのサーバは本当に安全ですか?今もっともイケてる脆弱性検知ツールVulsを使ってみた を参考にお試しください。謝辞
記事を書く際に、 @knqyf263 さん、 @sadayuki-matsuno さん、 @codehex さんにご意見をいただきました。ありがとうございました!!
以上です。
- 投稿日:2019-05-21T12:03:40+09:00
Docker Swarmでコンテナのオーケストレーション
Docker Swarmでコンテナのオーケストレーションを試したときの備忘です。
Docker環境が複数必要です。Docker Swarmとは
Docker に対応するネイティブなクラスタリング用ツールです。Docker Swarm は標準 Docker API で操作できます。そのため、Docker ホスト群を集め、1つの仮想 Docker ホストとして扱えます。
参考
http://docs.docker.jp/swarm/overview.htmlDocker Swarmを使ってクラスタ作成
//node1 $ docker swarm init --advertise-addr eth0 Swarm initialized: current node (4qmd5zm5h002y8ww8lwz5g07v) is now a manager. To add a worker to this swarm, run the following command: docker swarm join --token SWMTKN-1-31gbv0gje7royyy3x9i09iyd2h00hwtx8a21vlq23x2fxrje5n-48j2paz9czuz9fl2u4coo9euy 192.168.65.3:2377 To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.各nodeをクラスタに組み込む
上記コマンドでプリントアウトされたswarmの文をコピー(赤枠部分)
各nodeで実行
//node2 $docker swarm join --token SWMTKN-1-31gbv0gje7royyy3x9i09iyd2h00hwtx8a21vlq23x2fxrje5n-48j2paz9czuz9fl2u4coo9euy 192.168.65.3:2377 This node joined a swarm as a worker. //node3 $docker swarm join --token SWMTKN-1-31gbv0gje7royyy3x9i09iyd2h00hwtx8a21vlq23x2fxrje5n-48j2paz9czuz9fl2u4coo9euy 192.168.65.3:2377 This node joined a swarm as a worker. //node1でクラスタが組まれているか確認 docker nodes ls $ docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 811 * node1 Ready Active Leader 19.03.0-beta2 z08 node2 Ready Active 19.03.0-beta2 0xj node3 Ready Active 19.03.0-beta2Node1にコンテナ起動
//Nginxを起動 $docker service create nginx nginx //起動確認 $docker service ls ID NAME MODE REPLICAS IMAGE PORTS v80vj nginx replicated 5/5 nginx:1.12 *:80->80/tcpNgingxをスケール
//クラスタ内に5つレプリカを作成する $docker service update --replicas=5 --detach=true nginx //起動の確認 1 $docker service ps nginx : //起動の確認 2 $watch -n 1 docker service ps nginx→nginxがスケールされました。swarmがルーティングの制御もしています。
※curlでアクセスしてみるとレスポンスするnodeが変わるのが確認できます。所感
AWSのオートスケーリングみたいですねー。ただしswarmはport80しか対応していないらしい。。
からK8Sなのかー
- 投稿日:2019-05-21T10:27:16+09:00
[環境開発]Rails で scss が廃止された 話 by @penguin_fuyuno
結論
書き換える
# gem 'sass-rails', '~> 5.0' gem 'sassc-rails'理由
ただし、2019年3月に廃止予定であるため、乗り換えなければならないとのこと
作業向上labo
link: http://creative-agure.conohawing.com/efficiency_labo/?p=82Github
link: https://github.com/sass/sassc-railsエラー内容
Ruby Sass has reached end-of-life and should no longer be used. * If you use Sass as a command-line tool, we recommend using Dart Sass, the new primary implementation: https://sass-lang.com/install * If you use Sass as a plug-in for a Ruby web framework, we recommend using the sassc gem: https://github.com/sass/sassc-ruby#readme * For more details, please refer to the Sass blog: https://sass-lang.com/blog/posts/7828841環境
ruby '2.5.3' gem 'rails', '~> 5.2.2' gem 'mysql2', '>= 0.4.4', '< 0.6.0'
- 投稿日:2019-05-21T02:01:09+09:00
docker imageを直接pullしてみた
https://github.com/cielavenir/pulldockerimage
背景
userlandの仮想化基盤としてだけでなく、ソフトウェアの直接導入にもdockerは使われてきている。dockerはimageという単位で取り扱うことが多いが、このimageをリモートリポジトリからpullしたり、リモートリポジトリにpushしたりすることが可能である。これによりimageを共有することができるのである。
ところが一部、このpullを直接はできない環境があり得る。当該マシンが直接インターネットに繋がっていないことや、イントラネットにすら繋がっていないこともあるだろう。そのような場合、docker loadにより標準入出力を介してimageをやり取りすることになる。この場合に使われるのはpullしてきたファイル群をTarで固めたものである。これは手元のdocker daemonからdocker saveすることで作成することができるが、一旦手元にdocker pullしなければ、docker saveは実行できない。バッチ処理に組み込む場合等、不便なことがあるかもしれない。
今回は、リモートからダウンロードしたdocker imageのTarを直接標準出力に生成することを試みる。[1]はそうした試みの先駆けであるが、単体ではログイン機構を有しておらず、docker.ioで使うには力不足である。これをBashからRuby/Pythonで書き直し、再利用をしやすくしたい。方法
docker pullはHTTPSを用いており、認証 -> マニフェストダウンロード -> レイヤーダウンロード -> メタファイル生成という順序で行われる。
まずはマニフェストをダウンロードする。ここで401になったら、WWW-Authenticateヘッダの情報を用いて認証を行う。この認証はBasic認証であり、docker loginが済んでいれば、認証文字列は.docker/config.jsonから直接得ることができる。ここで帰ってきたtokenをBearer認証に用いる。レイヤーはマニフェストに書いてある順番で重ねれば良い。レイヤーのIDは[1]にならいSHA256(parentId+"\n"+layerDigest+"\n")
とした。結果
モジュール
Pythonは標準ライブラリで完結させることができた。Rubyはminitarとmechanizeについて外部ライブラリを利用する。minitarはTarライブラリで、mechanizeはプログラムベースブラウザとも言うべきウェブ操作ライブラリである。
WWW-Authenticateヘッダの取扱い
Pythonはurllib内にparse_keqv_listやparse_http_listといった関数群があり、これでparseすることができる。Rubyは標準ライブラリでは処理できないため、mechanizeを使用する。ただし、mechanizeはsavonというSOAPライブラリが導入されている環境に導入しようとするとruby-ntlm関係でエラーが発生することが知られているため、今回はMechanize::HTTP::WWWAuthenticateParserの中身を埋め込むこととした。
30xステータスコードの取扱い
mechanizeであれば30xステータスコードが来たら自動でLocationを読みに行ってくれるが、HTTPを直接触る限りそういうわけにも行かない。30xが来たら、今のレスポンスを捨てたあと、locationを読みに行き30xが来る限りレスポンスを捨てることを繰り返せば良い。この繰り返しはブロック(Pythonの場合はcontextmanagerを使う)を用いて行い、HTTP bodyはyieldで渡すようにすると、後処理が楽である。
Tarの取扱い
Tar(に限らずアーカイブファイル一般)はヘッダにメンバサイズを書かなければならない(またはアーカイブに追加したあとでシークが必要であるが、標準出力を扱うため認められない)。HTTPヘッダのContent-Lengthをこのメンバサイズとして書き込めば良い。Rubyの場合は、tar.add_fileではなくtar.add_file_simpleを使う必要があった。add_fileはメンバ内に書き込んだサイズを自動判別(しシーク)するバージョンだからである。
結論
今回はRuby/Pythonを用いてdocker imageをtarとしてpullすることができた。
https://github.com/cielavenir/pulldockerimage にて利用可能である。参考
[1] https://raw.githubusercontent.com/moby/moby/master/contrib/download-frozen-image-v2.sh
余談
(英語で書いたとしても)Linux Journalとかに載せるレベルじゃないですよねー肝心な場所のアルゴリズムは既存の技術ですしorz
7bgzfは違う領域だからある程度の価値があるけども。
- 投稿日:2019-05-21T01:42:23+09:00
Docker for WindowsでEnabled Kubernetesが出来なくてハマったときの解決方法
問題
- Docker for WindowsでEnabled KubernetesをチェックしApplyしたが、インストールが終わらなかったときの解決方法
対応内容
- firewallのoutboundルール追加
- 「.kube」ファイルを削除
- デフォルトに戻す
- 各種設定を変更
最後に
これで私は起動しました。参考 issuesは以下
Kubernetes fails to start (hangs in starting phase)
https://github.com/docker/for-win/issues/1962
- 投稿日:2019-05-21T01:21:43+09:00
Dockerfileを使用したコンテナ環境の作成ついて
Dockerfileを使用したコンテナ環境の作成について
- dockerを使って、素早く環境構築したい
- dockerfileをもっとスラスラ描きたいという思いから、基本的なことをまとめてみました。
- 下記の書籍を参考にしました。
- Docker/Kubenetes実践コンテナ開入門
- 間違いなどありましたら、コメントにて教えて頂けると、嬉しいです。
Dockerとは
- コンテナ型仮想化技術を実現、操作、管理するためのプロダクト
- コマンドラインで操作
- 良い特徴として
- インストールの難しいツールなどを使う場合でも、ホストを汚さずにDockerコンテナとして実行できる。
- 様々なライブラリやツールをコードで管理することができ、チームで開発環境を統一できる。
- Kubernetesなどを使ってコンテナを管理することにより、より簡単にデプロイやスケールを実現できる。 などなど
DockerイメージとDockerコンテナ
Dockerイメージ
- Dockerコンテナを構成するためのファイルシステムや実行するアプリケーション、設定をまとめたもの。コンテナを作成するためのテンプレート。
DockerHub
- Docker社が管理しているDockerレジストリで、Docker公式のイメージはDockerHubで公開されており、そこからpull(取得)して使う。自分で作成したイメージをDockerHubにpushすることで世界中のユーザに使用してもらうことも可能です。
# イメージ pull # タグ名は省略可能ですが、省略するとデフォルト(多くの場合latest)が指定されます。 # docker image pull [options] リポジトリ名[:タグ名] docker image pull golang:1.12 # イメージ push docker image push [options] リポジトリ名[:タグ]Dockerコンテナ
- Dockerイメージを基に作成され、アプリケーションが実行されている状態。Dockerによって作成されるゲストOS。
Dockerによる大まかなアプリケーション実行までの流れ
- Dockerイメージを定義...dockerfileを作成
- Dockerイメージを作成...dockerイメージをビルド
- 作成されたDockerイメージを使って、コンテナを作成実行
- コンテナを操作する
- コンテナの停止と開始、再起動と削除
- イメージの削除
1.Dockerイメージを定義...dockerfileの作成
Dockerfileとは
- Dockerイメージを作成するためのスクリプトのことです。イメージを作成するために必要な手順を記載していきます。
# Dockerfile FROM golang:1.12 RUN mkdir /echo_test COPY main.go /echo_test CMD ["go", "run", "/echo_test/main.go"]// コンテナ実行される、main.goファイル package main import ( "fmt" "log" "net/http" ) func main(){ http.HandleFunc("/",func(w http.ResponseWriter, r *http.Request){ log.Println("received request") fmt.Fprintf(w, "Hello Docker!!") }) log.Println("start server") sv := &http.Server{Addr: ":8080"} if err := sv.ListenAndServe();err != nil{ log.Println(err) } }
FROM
やRUN
と行ったキーワードを「インストラクション(命令)」と言います。- Dockerfileではインストラクションを使って、イメージの構成を定義します。
- 上記のDocerfileでやっていること
FROM
で作成するDockeイメージのベースとなるイメージを指定しています。この場合、公式のgolang:1.12が指定されていて、golang:1.12のイメージがまだpullされていなければ、DockerHubからpullし、すでにpullされていれば既存のgolang:1.12のイメージを使用します。RUN
でDockerイメージをビルドする時に、Dockerコンテナ内で実行するコマンドを定義しています。この場合、/echo_testというディレクトリをコンテナ内に作成しています。COPY
で、ホストマシン上のファイルやディレクトリをDockerコンテナ内にコピーしています。この場合、Dockerfileと同じディレクトリ内にあるmain.goというファイルをコンテナ内の/echo_testディレクトリにコピーしています。CMD
でDockerコンテナとして実行する際にコンテナ内で実行するプロセスをしています。 CMDはコンテナ起動時に1度実行されます。この場合、以下のようなコマンドを実行してgoのプロセスを実行します。go run /echo_test/main.go
- Dockerfileに記載した各項目(この場合、
FROM
,RUN
,COPY
,CMD
)が、それぞれ1つのレイヤーという単位で、そのレイヤーが重なって、イメージが作成されます。FROMで指定したイメージがベースとなるというのは、このような仕組みのためです。2.Dockerイメージを作成...Dockerイメージをビルド
- Dockerfileを使用して、Dockerイメージをビルドします。
# docker image build -t イメージ名[:タグ名] Dockerfileのあるディレクトリ docker image build -t example_go/echo:latest .
docer image build
でイメージをビルドします。-t
オプションを必ずつけたほうが良いです。-t
オプションにより、イメージ名を指定することができます。-t
オプション無しでもイメージの作成はできますが、イメージ名(REPOSITORY)とタグ(TAG)の値が<none>
になります。ハッシュ値(IMAGE ID)での管理となり、わかりづらくなります。$ docker image ls # docker imageの一覧を表示 REPOSITORY TAG IMAGE ID CREATED SIZE <none> <none> a086e6cb87f0 53 seconds ago 774MB example_go/echo latest 806faed5d0a8 About an hour ago 774MB
docker image ls
コマンドでexample_go/echo
のイメージが作成されていることが確認できました。3. 作成されたDockerイメージを使って、コンテナを作成実行
- 作成したイメージからコンテナを実行します。
- コマンドは
docker container run イメージ名
となりますが、いくつかオプション指定したほうが良いです。# docker container run [options] イメージ名:[タグ名] docker container run -d -p 9000:8080 example_go/echo:latest
-d
オプションで、コンテナをバックグラウンドで実行します。今回のようなサーバアプリケーションだとバックグラウンドで実行したほうが勝手が良いことが多いと思います。ただrailsなどをコンテナで実行する時に、あえて-d
オプション無しで実行し、logを確認しやすくするようなこともあります。
-p
オプションでポートフォワーディングを設定します。今回のアプリケーションは8080のポートで実行されていますが、それはコンテナ内のことで、ポートフォワーディングの設定が無い場合、コンテナの外からポート8080にアクセスしても、コンテナ内のアプリケーションに到達できません。コンテナ外からきたリクエストをコンテナ内で実行しているアプリケーションに到達させるために、
-p {ホスト側ポート}:{コンテナ側ポート}
で、ホスト側の指定のポートにアクセスすると、コンテナ側の指定したポートでリクエストを送ることができます。また、
-p
オプションで、ホスト側ポートを指定しなかった場合、どのポートにアクセスするば良いのか調べる方法もあります。$ docker container run -d -p 8080 example_go/echo:latest 2bdfce9e69c6b1ed81a7d6ea0e493e0867effa233d3f89aa8e27a793e1f29f90 $ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2bdfce9e69c6 example_go/echo:latest "go run /echo_test/m…" 3 seconds ago Up 2 seconds 0.0.0.0:32769->8080/tcp elated_roentgen
- PORTSに記載されている、
0.0.0.0:32769
がホスト側のポートになります。この場合、localhost:32769
にアクセスすると、Hello Docker!!
と表示されます。4.コンテナを操作する
コンテナで任意のコマンドを実行する
# docker container exec [options] コンテナIDまたはコンテナ名 実行するコマンド # 今回は例として、先ほど実行したコンテナに入ります。 docker container exec -it 2bdfce9e69c6 bash root@2bdfce9e69c6:/go# root@2bdfce9e69c6:/go# pwd /go root@2bdfce9e69c6:/go#
docker container exec
コマンドは実行しているDockerコンテナの中で任意のコマンドを実行できます。- 標準入力を開いたままにする
-i
オプションと仮想ターミナルを割り当てる-t
オプションを組み合わせて、対話的にシェルコマンド(bash)を実行しています。- コンテナの中に入るときは
-it
をつけると覚えておくと便利です。コンテナとホスト間でファイルのコピーをする
# docker container cp [options] コンテナIDまたはコンテナ名:コンテナ内のコピー元 ホストのコピー先 # コンテナ内の/echo_test/main.go を ホストの ~/以下にコピー docker container cp 2bdfce9e69c6:/echo_test/main.go ~/# docker container cp [options] ホストのコピー元 コンテナIDまたはコンテナ名:コンテナ内のコピー先 # ホストの ~/.ssh/id_rsa をコンテナ内の/echo_test以下にコピー docker container cp ~/.ssh/id_rsa 2bdfce9e69c6:/echo_test
- 上記のように
cp
コマンドでコンテナとホスト間でファイルのコピーができます。- 例にあるように、ホストssh_keyをコンテナにコピーして、コンテナからGitHubのリポジトリにアクセスするなども可能です。
5.コンテナの停止と開始、再起動と削除
- 先ほど使用した、
docker container ls
コマンドでコンテナのIDやコンテナ名を確認することができます。- 確認したコンテナIDやコンテナ名を引数にして下記のコマンドを実行することで、コンテナの停止と開始、再起動と削除ができます。
- コンテナの停止
docker container stop コンテナIDまたはコンテナ名
- コンテナの開始
docker container start コンテナIDまたはコンテナ名
- コンテナの再起動
docker container restart コンテナIDまたはコンテナ名
- コンテナの削除
- コンテナの削除はコンテナが停止している状態でないと基本的には実行できません。
- ただ
docker rm -f コンテナIDまたはコンテナ名
のように、-f
オプションで強制削除することもできます。docker container rm コンテナIDまたはコンテナ名6.イメージの削除
docker image ls
またはdocker images
コマンドでイメージの一覧を確認できます。- そこで表示された対象のイメージのIDを引数に指定することで、イメージを削除します。
docker image rm イメージID # または docker rmi イメージID
最後に
- dockerfileだけでも簡単に環境構築ができます。
- 他にも便利なコマンドがありますので、公式ドキュメントや、
docker help
コマンドで確認できます。- ComposeやSwarm、Kubernetesなどについても、まとめていきます。
- 投稿日:2019-05-21T00:05:12+09:00
Windows10のWSLでdocker-compose
概要
- docker使いたい
- コマンドプロンプトとかPowerShellきらい
- WSLあるし使おう
マウントがうまく行かずハマる
TL;DR
マウントのパスは相対パスで書かないこと.絶対パスで書く
Windowsのパス形式で書かないこと.やりたいこと
WSLでdocker-compose up できるようにする
WSLが有効化されている状態で始めます
WSLでdockerのインストールからdocker-composeまで動かす
を参考にWSLにdocker-composeのインストールまで済ませるDocker DesktopのSettingsでShare DrivesのCドライブを一旦チェック外してApply
もう一回CドライブにチェックいれてApply 参考
docker-compose.ymlのvolumesの中でパスを指定するときに絶対パスで指定する
- 相対パスだと,永続化はされるがホスト側の見当違いなところにマウントされる
- Windowsのパス形式で書いてもWSLのdocker-composeは理解できない