- 投稿日:2020-09-13T23:37:12+09:00
【初心者向けワークショップ】Cloud9+Docker Compose+CypressでE2Eテストを書いてみよう!
はじめに
【初心者向けワークショップ】Cloud9+React+TypeScript+Amplify+Cypressでアプリの公開からCI/CD+E2Eテスト自動化までやってみよう!でアプリケーションの公開からCI/CD、E2Eのテストまで一通りの流れを体験する記事を書きました。
今回は、アプリケーションを開発しながらE2Eのテストを少しずつ育てていく工程をサクッと体験するチュートリアルを作ってみました。
1時間程度で試すことができる内容にしています!開発環境の構築
クラウドベースの統合開発環境(IDE)であるAWS Cloud9を使用します。
Cloud9上でDocker Composeが動かせる環境を構築していきます。
CypressはDockerのビルド時にインストールされるため、npmコマンド等のインストールは不要です。AWSアカウントの取得がまだの方は公式のAWSアカウント作成の流れを参照して
アカウントを取得しておいてください。AWS Cloud9のセットアップ
- AWS マネジメントコンソールにログインして、サービスからAWS Cloud9を選択する
- 「Create environment」ボタンをクリックする
- Name environmentの項目を入力して(以下は入力例)、「Next Step」ボタンをクリックする
- Configure settingsの項目を入力して(以下は入力例)、「Next Step」ボタンをクリックする
- 設定内容を確認して「Create Environment」をクリックする
AWS Cloud9で利用しているEBSボリューム領域を拡張する
ワークショップを進める上で、Dockerのビルドを行うため、ビルドの実行する工程で領域不足になります。
こちらの記事を参考にして事前にボリュームを拡張しておくことをお勧めします。
私は余裕を持って、10GB -> 20GBに増やしておきました。
AWS Cloud9 で利用しているEBS ボリューム領域を拡張する必要なパッケージのインストール
Docker Compose
Dockerは標準装備されていますが、Docker Composeは入っていないためインストールする必要があります。(2020/09/09時点)
ec2-user:~/environment $ docker -v Docker version 19.03.6-ce, build 369ce74 ec2-user:~/environment $ docker-compose -v bash: docker-compose: command not found公式ドキュメントの手順を参考にしてインストールします。
Docker Composeの現在の安定リリースバージョンをダウンロード
以下は、2020/09/09時点での最新を取得しています。
$ sudo curl -L "https://github.com/docker/compose/releases/download/1.27.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose $ sudo chmod +x /usr/local/bin/docker-compose $ docker-compose -v docker-compose version 1.27.0, build 980ec85bCypressを使ったE2Eテストを試すサンプルコードの取得
Dockerを使ったローカルのアプリケーションに対してCypressでE2Eテストを行えるサンプルコードがGithubで公開されていますので、今回はこちらを利用します。
https://github.com/cypress-io/cypress-example-docker-composeサンプルコードの取得
今回はGithubへのコミット、またそれをトリガーにしたCI/CDなどは絡めませんので、GithubリポジトリのForkなどはせず、本家のリポジトリをクローンしてきます。
httpsの場合$ git clone https://github.com/cypress-io/cypress-example-docker-compose.git $ cd cypress-example-docker-composesshの場合$ git clone git@github.com:cypress-io/cypress-example-docker-compose.git $ cd cypress-example-docker-composeコンテナのビルド
Dockerコンテナのビルドを実行します。
$ docker-compose buildが実行されます。$ npm run build > cypress-example-docker-compose@1.0.0 build /home/ec2-user/environment/cypress-example-docker-compose > docker-compose build Building web Step 1/4 : FROM httpd:2.4 ---> a6ea92c35c43 Step 2/4 : RUN echo "ServerName localhost" >> /usr/local/apache2/conf/httpd.conf ---> Using cache ---> e132eaf0b6d6 Step 3/4 : COPY index.html /usr/local/apache2/htdocs/ ---> Using cache ---> a79afef5fb17 Step 4/4 : EXPOSE 80 ---> Using cache ---> 0ba9c6797d6d Successfully built 0ba9c6797d6d Successfully tagged apache:latest Building e2e Step 1/7 : FROM cypress/base:10 10: Pulling from cypress/base d6ff36c9ec48: Pull complete c958d65b3090: Pull complete edaf0a6b092f: Pull complete 80931cf68816: Pull complete bc1b8aca3825: Pull complete ad9790d89c32: Pull complete 6085b6a0249c: Pull complete 6af9e71c78d2: Pull complete d85bae49b22d: Pull complete f6c8ce594b00: Pull complete d67d7860a80a: Pull complete b3a1dfd049d1: Pull complete 6fb47a9e5454: Pull complete Digest: sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Status: Downloaded newer image for cypress/base:10 ---> 071155d6ed07 Step 2/7 : WORKDIR /app ---> Running in 468f37420452 Removing intermediate container 468f37420452 ---> 2f1c6d19e291 Step 3/7 : COPY package.json . ---> 90ec115cb213 Step 4/7 : COPY package-lock.json . ---> 54e720d60a87 Step 5/7 : ENV CI=1 ---> Running in 5e56a602e37c Removing intermediate container 5e56a602e37c ---> 670f913b90e6 Step 6/7 : RUN npm ci ---> Running in d511a99685c5 > cypress@5.1.0 postinstall /app/node_modules/cypress > node index.js --exec install [14:40:14] Downloading Cypress [started] [14:40:17] Downloading Cypress [completed] [14:40:17] Unzipping Cypress [started] [14:40:33] Unzipping Cypress [completed] [14:40:33] Finishing Installation [started] [14:40:33] Finishing Installation [completed] added 216 packages in 25.818s Removing intermediate container d511a99685c5 ---> 8dc49d0f8503 Step 7/7 : RUN npx cypress verify ---> Running in aa9a7fb8a0d9 [14:41:10] Verifying Cypress can run /root/.cache/Cypress/5.1.0/Cypress [started] [14:41:14] Verifying Cypress can run /root/.cache/Cypress/5.1.0/Cypress [completed] Removing intermediate container aa9a7fb8a0d9 ---> 0a5f502ade2e Successfully built 0a5f502ade2e Successfully tagged cypress:latestWebアプリケーションの起動とCypressを使ったE2Eテストの実行
まず、何も手を入れない状態でテストが動くことを確認しましょう。
そして、Cypressを使うとどんな嬉しいことがあるのか見てみましょう。
$ docker-compose buildが実行されます。$ npm run up > cypress-example-docker-compose@1.0.0 up /home/ec2-user/environment/cypress-example-docker-compose > docker-compose up --abort-on-container-exit --exit-code-from e2e Creating network "cypress-example-docker-compose_default" with the default driver Creating apache ... done Creating cypress ... done Attaching to apache, cypress apache | [Wed Sep 09 14:44:05.645451 2020] [mpm_event:notice] [pid 1:tid 140649514787968] AH00489: Apache/2.4.46 (Unix) configured -- resuming normal operations apache | [Wed Sep 09 14:44:05.664458 2020] [core:notice] [pid 1:tid 140649514787968] AH00094: Command line: 'httpd -D FOREGROUND' cypress | cypress | ==================================================================================================== cypress | cypress | (Run Starting) cypress | cypress | ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ cypress | │ Cypress: 5.1.0 │ cypress | │ Browser: Electron 83 (headless) │ cypress | │ Specs: 1 found (spec.js) │ cypress | └────────────────────────────────────────────────────────────────────────────────────────────────┘ cypress | cypress | cypress | ──────────────────────────────────────────────────────────────────────────────────────────────────── cypress | cypress | Running: spec.js (1 of 1) apache | 172.18.0.3 - - [09/Sep/2020:14:44:15 +0000] "GET / HTTP/1.1" 200 27 cypress | cypress | (Results) cypress | cypress | ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ cypress | │ Tests: 1 │ cypress | │ Passing: 1 │ cypress | │ Failing: 0 │ cypress | │ Pending: 0 │ cypress | │ Skipped: 0 │ cypress | │ Screenshots: 0 │ cypress | │ Video: true │ cypress | │ Duration: 0 seconds │ cypress | │ Spec Ran: spec.js │ cypress | └────────────────────────────────────────────────────────────────────────────────────────────────┘ cypress | cypress | cypress | (Video) cypress | cypress | - Started processing: Compressing to 32 CRF cypress | - Finished processing: /app/cypress/videos/spec.js.mp4 (1 second) cypress | cypress | cypress | ==================================================================================================== cypress | cypress | (Run Finished) cypress | cypress | cypress | Spec Tests Passing Failing Pending Skipped cypress | ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ cypress | │ ✔ spec.js 342ms 1 1 - - - │ cypress | └────────────────────────────────────────────────────────────────────────────────────────────────┘ cypress | ✔ All specs passed! 342ms 1 1 - - - cypress | cypress exited with code 0 Aborting on container exit... Stopping apache ... done嬉しいことその1
勝手に動画を撮って残してくれます。
動画の保存場所$ ls -l e2e/cypress/videos/spec.js.mp4 -rw-r--r-- 1 root root 29169 Sep 9 14:44 e2e/cypress/videos/spec.js.mp4AWS Cloud9で動画や画像を閲覧する際は、以下のようにファイルにマウスカーソルをフォーカスさせて、右クリックで「Preview」を選択すると閲覧できます。
[e2e/cypress/videos/spec.js.mp4をgifに変換したもの]
嬉しいことその2
Seleniumだと、失敗する可能性がある箇所に
page.save_screenshot 'failed.png'のようなコードを挿入してスクリーンショットを残したりすることがあります。
Cypressは特に工作をしなくてもデフォルトでテスト失敗時には静止画を残してくれます。
動画も残るのでそこからも確認できますが、ズバリここ!みたいなのは静止画の方がわかりやすいです。
以下は、期待結果「Hi there」と表示されることの検証を「Hey there」に変えて失敗させた例です。$ git diff diff --git a/e2e/cypress/integration/spec.js b/e2e/cypress/integration/spec.js index 5e90f2d..809de14 100644 --- a/e2e/cypress/integration/spec.js +++ b/e2e/cypress/integration/spec.js @@ -1,4 +1,4 @@ it('loads page', () => { cy.visit('/') - cy.contains('Hi there') + cy.contains('Hey there') })$ npm run up > cypress-example-docker-compose@1.0.0 up /home/ec2-user/environment/cypress-example-docker-compose > docker-compose up --abort-on-container-exit --exit-code-from e2e Starting apache ... done Starting cypress ... done Attaching to apache, cypress apache | [Wed Sep 09 14:55:49.753051 2020] [mpm_event:notice] [pid 1:tid 140399861245056] AH00489: Apache/2.4.46 (Unix) configured -- resuming normal operations apache | [Wed Sep 09 14:55:49.753761 2020] [core:notice] [pid 1:tid 140399861245056] AH00094: Command line: 'httpd -D FOREGROUND' cypress | cypress | ==================================================================================================== cypress | cypress | (Run Starting) cypress | cypress | ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ cypress | │ Cypress: 5.1.0 │ cypress | │ Browser: Electron 83 (headless) │ cypress | │ Specs: 1 found (spec.js) │ cypress | └────────────────────────────────────────────────────────────────────────────────────────────────┘ cypress | cypress | cypress | ──────────────────────────────────────────────────────────────────────────────────────────────────── cypress | cypress | Running: spec.js (1 of 1) apache | 172.18.0.3 - - [09/Sep/2020:14:55:59 +0000] "GET / HTTP/1.1" 200 27 cypress | cypress | (Results) cypress | cypress | ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ cypress | │ Tests: 1 │ cypress | │ Passing: 0 │ cypress | │ Failing: 1 │ cypress | │ Pending: 0 │ cypress | │ Skipped: 0 │ cypress | │ Screenshots: 1 │ cypress | │ Video: true │ cypress | │ Duration: 4 seconds │ cypress | │ Spec Ran: spec.js │ cypress | └────────────────────────────────────────────────────────────────────────────────────────────────┘ cypress | cypress | cypress | (Screenshots) cypress | cypress | - /app/cypress/screenshots/spec.js/loads page (failed).png (1280x720) cypress | cypress | cypress | (Video) cypress | cypress | - Started processing: Compressing to 32 CRF cypress | - Finished processing: /app/cypress/videos/spec.js.mp4 (2 seconds) cypress | cypress | cypress | ==================================================================================================== cypress | cypress | (Run Finished) cypress | cypress | cypress | Spec Tests Passing Failing Pending Skipped cypress | ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ cypress | │ ✖ spec.js 00:04 1 - 1 - - │ cypress | └────────────────────────────────────────────────────────────────────────────────────────────────┘ cypress | ✖ 1 of 1 failed (100%) 00:04 1 - 1 - - cypress | cypress exited with code 1テスト失敗時に撮影される静止画の保存場所$ ls -l e2e/cypress/screenshots/spec.js/loads\ page\ \(failed\).png -rw-r--r-- 1 root root 29790 Sep 9 14:56 e2e/cypress/screenshots/spec.js/loads page (failed).png[e2e/cypress/screenshots/spec.js/loads\ page\ (failed).png ]
「Hi there」を期待しているのに「Hey there」になっていることがバッチリわかりますね!
ワークショップ 〜テストを書きながら開発してみよう〜
テスト駆動開発(Test-Driven Development: TDD)
テスト駆動開発(Test-Driven Development: TDD)とは、テストファーストなプログラムの開発手法です。
つまり、プログラムの実装前にテストコードを書き、そのテストコードに適合するように実装とリファクタリングを進めていく方法を指します。
テスト駆動開発は、「Red」「Green」「Refactoring」という順序で進められます。
テスト駆動開発をすることで以下の利点があります。
- 後工程へバグを持ち越すことを未然に防ぐことができる
- 開発の初期段階で不具合を検知・修正できる
- テストを書く=早い段階で仕様を理解できる
- 開発が進んでいく中で、「そういえばどうなんだっけ。。。」となるのを防げる
- 安心して開発できる
- テストを先に作っておくことで、アプリコードを書いていってもテストを要所要所で実行しておけば壊してないことを常に担保できる
Red
- 実装した機能の要件通りになっていることを担保するテストコードを書く
- テストは失敗する
Green
- どのようなコードでも良いので要件を満たし、テストが成功するコードを書く
- テストは成功する
Refactoring
- テストが成功する状態を維持しつつ簡潔・明快なコードに修正する
実装したい機能のプログラムよりもテストコードを先に書くため、はじめはテストに失敗しますが、プログラムの実装と修正を短いサイクルで何度も繰り返してバグをなくし、正しく動作するコードが書けたらリファクタリングを行います。
ここからは、自由に機能要件を考えていただいてテスト実装、アプリ実装を進めてみてください。
要件の例を挙げておきます。
ぜひ参考にしてオリジナルの仕様を決めて進めてみてください。
Webページの実装にはHTMLやCSSを使用します。
HTMLを書く際には、<meta charset="utf-8"/>を書き忘れると日本語が文字化けしてしまうのでご注意ください。
また、CSSについては[覚え書き] CSS再入門~セレクタ~をご覧ください。[要件例]
自己紹介ページを作成する。
自己紹介には、以下が含まれている。
- 名前
- 年齢
- 趣味
[実装するページのイメージ画像]
1. 実装した機能の要件を満たすことを確認するテストを書く
「これ、どうやってテスト書いたらいいんだろう?」という方は、コマンド集も作りましたので参考にしてみてください。
これだけはおさえておきたいCypressコマンド集e2e/cypress/integration/spec.jsit('load page', () => { cy.visit('/') cy.get('.title').should('have.text', 'RustyNailの部屋') cy.get('.summary').should('have.text', 'これはRusty Nailの自己紹介のページです。') cy.get('.content__name').should('have.text', 'Rusty Nail') cy.get('.content__age').should('have.text', '34歳') cy.get('.content__hobby').should('have.text', '卓球') })2. テストを実行する(Red : 失敗する)
$ npm run upを実行して、テストを走らせます。
まだ何もテストを書いていないため当然失敗します。3. アプリケーションを実装する
webapp/index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <title>RustyNailの部屋</title> </head> <body> <h1 class='title'>RustyNailの部屋</h1> <h2 class='summary'>これはRusty Nailの自己紹介のページです。</h2> <ul class='content'> <li class='content__name'>Rusty Nail</li> <li class='content__age'>34歳</li> <li class='content__hobby'>卓球</li> </ul> </body> </html>
$ npm run buildを実行して、webapp/index.htmlの変更をDockerイメージに更新します。4. テストを実行する(Green : 成功する)
$ npm run upを再度実行して、テストを走らせます。
要件を満たすアプリケーションの実装も完了したため、テストは成功します。このような感じで、次は「特技」「好きな食べ物」など追加していく場合、
1つテストを書いて1つ実装を繰り返していきます。おまけ
ワークショップでは、デフォルトブラウザを使用しましたが、各種ブラウザがインストールされていれば指定することができます。
使用した(デフォルト)ブラウザcypress | ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ cypress | │ Cypress: 5.1.0 │ cypress | │ Browser: Electron 83 (headless) │ cypress | │ Specs: 1 found (spec.js) │ cypress | └────────────────────────────────────────────────────────────────────────────────────────────────┘ワークショップでは、ブラウザのインストールなどはDocker環境を整備することになるため割愛しています。
実行するブラウザの指定
以下のようにブラウザを指定できます。
対応ブラウザの詳細は、公式のLaunching Browsersをご覧ください。chromeを指定した例$ cypress run --browser chromeheadlessの指定
ヘッドレスで実行したい場合は、以下のように
--headlessオプションを指定します。$ cypress run --browser chrome --headless
- 投稿日:2020-09-13T22:05:00+09:00
Ultra96v2の開発環境をDocker上に構築する1
先日Ultra96v2を購入しLinuxとXilinxの環境構築に挑戦してみました。ので久しぶりにQiitaで記事を書いてみます。記事は随時更新予定です。予定としては、Ubuntu20.04LTSにDockerをインストールし、その上にXilinxの開発環境を構築していきます。
購入編
AVNETさんから下記を購入しました。
No. Avnet Part No 説明 1 AES-ULTRA96-V2-G Ultra96V2ボード本体です。 2 AES-ACC-U96-JTAG JTAG基板です。USBシリアル接続ができます。 3 送料が600円ほどかかります。 その他最低限下記が必要です。
- ACアダプター:DC12V 2A以上。
- ACアダプターのプラグφ1.7への変換ケーブル:標準プラグからUltra96V2用のプラグへ変換します。
- Micro USB(USB A-MicroB)ケーブル:Ultra96V2へシリアル接続するのに使います。
- SDカード用USBリーダーライター:Panasonic BN-SDCKP3など。PCからのSDカードアクセスに使用します。
- USB 有線イーサ―ネットアダプタ:BUFFALO LUA3-U2-ATXなど。Ultra96V2のWifiが不安定な時にあると良いです。
私の場合、DC12Vは手持ちのUSB PDからUSB-PD_Adapterを使用して12Vを取り出し直接φ1.7のプラグへ接続しました。
上図の12V電源ケーブルのXH2ピンコネクタは赤黒の電線付きが千石電商等で売っていますので利用すると良いです。が、赤がプラスで黒がマイナスのシキタリがありますのでコンタクトを細い千枚通し等で外して反対にします。ACアダプターのプラグはセンタープラスです。
その他、Ultra96 基本講座:Unboxing編や はじめてのUltra96 必要なもの を参考に揃えてください。
開封の儀
届いたら内容物を確認し、取り合えずLチカをすると良いです。Ultra96基本講座:Lチカ編等が参考になりました。
箱の中にXilinxのライセンス登録書も入っていました。ですが、どこで使うのか分からないので未だ登録していません。Ultra96V2 環境構築 先行調査編
Xilinxの高位合成ツールのVitisを使用してC言語から回路を作成して、Ultra96V2で動作させる事を目指します。とりあえず、先行している情報を調べてまとめてみました。
basaroさん
Ultra96用Vitisプラットフォームの作り方(BASE編)
Ultra96V2向けWifi対応Vitisプラットフォーム(OOB版)Ryuzさん
Ultra96V2環境構築メモmarseeさん
FPGAの部屋 Vitis
PetaLinux 2020.1 のインストール
XRT 2020.1 をインストール
Ultra96-V2 の Vitis 2020.1 アクセラレーション・プラットフォームを作る1(ハードウェア・コンポーネント編)
Ultra96-V2 の Vitis 2020.1 アクセラレーション・プラットフォームを作る2(ソフトウェア・コンポーネント編)
Ultra96-V2 の Vitis 2020.1 アクセラレーション・プラットフォームを作る3(Vitis 2020.1 でアクセラレーション・プラットフォーム作成)
Ultra96-V2 の Vitis 2020.1 アクセラレーション・プラットフォームを作る4(Vitis 2020.1 でアプリケーション・プロジェクトを作成)
Ultra96-V2 の Vitis 2020.1 アクセラレーション・プラットフォームを作る5(Vitis 2020.1 で作成したアプリを動作されるが動かない!)
Ultra96-V2 の Vitis 2020.1 アクセラレーション・プラットフォームを作る6(Vitis 2020.1 で作成したアプリを動作されるが動かない! 2)所感:大変苦労しそうな予感がヒシヒシと伝わってきます。
Ultra96V2開発機器の説明
- 開発ホスト:Ubuntu20.04をインストールした10年モノのパソコンです。参考ですがスペックはCore2Quadマシンに16GBytesメモリと512GBytesSSDと10TBytesHDDを搭載してあります。今では弱々パソコンです。ヘッドレスで使用しています。
- Ultra96V2:今回のターゲットです。
- パソコン:Windows10をインストールしたごく普通のパソコンです。開発ホストへのリモートデスクトップ接続や、Ultra96V2へMobaXtermからシリアル接続&イーサネット接続する時に使用しています。
開発ホストのみで環境構築可能です。しかしWindows10からリモートデスクトップやSSH接続で開発ホストやターゲット側にアクセスする方が何かと便利なので分けています。WSL2も検討したのですが、パソコン上のVMWareと共存できないのでアキラメました。
Ubuntu20.04のインストール
開発ホストにUbuntuをインストールします。RDPのインストールまで、キーボードとマウスとモニターを開発ホストに接続して操作をします。
Ubuntu Desktop 日本語 Remix Ubuntu20.04を使用しました。
ファイル名: ubuntu-ja-20.04-desktop-amd64.iso
インストール後、GUIから初期設定をします。
- 右上▽設定→日付と時刻→タイムゾーン(O) JST(日本,Tokyo)
- 右上▽設定→地域と言語→フォーマット(F) 日本
- 左上アクティビティ→検索でU→ソフトウェアの更新
- デスクトップ上で右クリック→端末で開く
開いた端末(gnome-terminal)の、左上の+アイコンをクリックするとタブウィンドウが開きます。CTRL-Dで閉じます。
LinuxのVersionは下記でした。bash$uname-a Linux ubuntu 5.4.0-47-generic #51-Ubuntu SMP Fri Sep 4 19:50:52 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux次に画面左上のアクティビティから検索にUを入力して、ソフトウェアの更新を開きます。この更新が終わるのを待ちます。再起動を求められた時は再起動します。
SSH Serverのインストール
apt$sudo apt install -y openssh-serverRDPのインストール
第621回 Ubuntu 20.04 LTSでxrdpを使用するを参照しながらxrdpをインストールします。インストール後に、
- 右上▽設定→地域と言語→インストールされている言語の管理
から、日本語をシステム全体に適用してください。地域フォーマットも同様に全体に適用してください。
下図赤丸のボタンです。
やらなかった場合には、リモートデスクトップ接続時に日付がせぷてんばぁーになる事があります。最後に確認のため、Windows10のパソコンからUbuntuの開発ホストにリモートデスクトップ接続をします。日付、左のサイドバー、マウス右クリックで端末を開ける事を確認してください。その後、GUIからログアウトします。
以降はリモート接続で操作可能です。開発ホストからキーボード、ビデオ、マウスを外してヘッドレス運用へ移行します。Dockerのインストール
開発ホストにDockerを入れます。
docker_install1sudo apt install -y curl curl https://get.docker.com > install.sh念のため、install.shの内容を確認します。installには管理者権限が必要です。OKなら
docker_install2chmod +x install.sh ./install.sh sudo usermod -aG docker $USERでインストールします。なお、sudo機能はデフォルトで5分間、sudoで別のコマンドをパスワード無しで実行する事ができます。
つづく
- 投稿日:2020-09-13T21:53:20+09:00
RとPythonのDocker作業環境の構築2: 日本語対応
はじめに
先日に以下の記事を投稿し,RとPythonが使える環境を構築するDockerfileを紹介しました。
こちらの記事のDockerfileでは,以下の問題がありました。
- 日本語の対応ができていない
- 文字コードが異なっており,他OSのテキスト解析の結果を再現できない
そこで,localeを変更することで,上記問題を解決するDockerfileに修正しました。
FROM ubuntu:18.04 # set timezone RUN apt-get update \ && apt-get install tzdata \ && ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime RUN date # install packages RUN ["/bin/bash", "-c", "\ apt-get update \ && apt-get install -y \ vim \ build-essential \ git curl llvm sqlite3 libssl-dev libbz2-dev \ libreadline-dev libsqlite3-dev libncurses5-dev \ libncursesw5-dev python-tk python3-tk tk-dev aria2 \ lsb-release locales\ "] RUN locale-gen ja_JP.UTF-8 ENV LANG ja_JP.UTF-8 ENV LANGUAGE ja_JP:ja ENV LC_ALL ja_JP.UTF-8 RUN ["/bin/bash", "-c", "apt-get install -y software-properties-common"] RUN apt-add-repository ppa:ansible/ansible -y # install r RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9 #RUN add-apt-repository 'deb https://cran.rstudio.com/bin/linux/ubuntu $(lsb_release -cs)-cran35/' RUN add-apt-repository 'deb https://cran.rstudio.com/bin/linux/ubuntu bionic-cran35/' RUN ["/bin/bash", "-c", "\ apt-get update \ && apt-get install -y r-base \ "] RUN Rscript --version CMD ["/bin/bash", "-c"]差分は以下の通りです。
- python3.8 python3-pip のインストールをやめた
- インストールするパッケージにlocalesを追加した
- locale-genから始まるブロックで,日本語に設定した
以上です。
コンテナ上でpyenvを構築するためのスクリプトを作成中で,これができたらとりあえずPCを変えても再現できる環境が作れるのではないかと思っています。
スクリプトができたらまた記事書きます。
- 投稿日:2020-09-13T20:48:24+09:00
Docker Quiz を解いてみる
Docker Quiz とは
二時間目コンテナの講義ではDocker Quizが出題。君は、すべて解くことができるか?
— hatenatech@はてなの技術情報アカウント (@hatenatech) September 7, 2020
$ docker run --rm -i hatena/intern-2020-docker-quiz #hatenaintern2020まだ挑戦したことがない方は是非この記事を読む前に挑戦してみてください。Docker の知識があまりなくても、調べれば回答できる問題が多いです。
後に講義の動画もアップされるようです。
解いてみる
Q1 [sanity check] 次の文字列を入力してください: FLAG_3u8z4xst
sanity check です。
FLAG_3u8z4xstと入力します。Q2 -q2 オプション引数を渡して起動せよ
docker run --rm hatena/intern-2020-docker-quiz -q2のようにオプション引数付きで起動すると答えが表示されます。ちなみに
-hintオプションをつけて起動すると、各問題のヒントを見ることができます。Q3 このイメージのentrypointとして指定されているコマンドのフルパスは何か
docker inspect hatena/intern-2020-docker-quizでこのイメージの情報を見ることができます。その中に entrypoint が記載されているので、入力します。Q4 /app/flag.txt をホストにコピーしてファイルの内容を取得せよ
docker cp <コンテナ名 or コンテナ ID>:/app/flag.txt ./flag.txtのようにしてホストにコピーできます。
コンテナ名はdocker psなどから確認できます。Q5 シェルを起動して /app/get_flag2.exe を実行せよ
docker run --rm --entrypoint /app/get_flag2.exe hatena/intern-2020-docker-quizのようにして entrypoint を指定して実行します。Q6 このイメージに含まれるpythonについて、Trivyによって検出されるseverityがHIGHの脆弱性のCVE番号を答えよ
Trivy というコンテナの脆弱性スキャナーを使います。
下記のコマンドで簡単にスキャンできます(macOS)。
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v $HOME/Library/Caches:/root/.cache/ aquasec/trivy image hatena/intern-2020-docker-quizスキャンが完了すると脆弱性のあるパッケージの一覧が表示されるので、python パッケージの中から severity(重症度)が HIGH の CVE 番号(共通脆弱性識別子)を探して入力する...
...はずですが、python で脆弱性が HIGH の CVE 番号は見つかりませんでした。原因は分かりませんでしたが、表示された python の CVE 番号を片っ端から入力していったら正解することができました。何かご存知の方がいましたら教えていただけるとありがたいです。
(補足)オプションに
--severity HIGHを指定すると severity が HIGH のものだけを表示できます。
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v $HOME/Library/Caches:/root/.cache/ aquasec/trivy image --severity HIGH hatena/intern-2020-docker-quizQ7 このイメージのentrypointとして指定されているコマンドのソースコードを復元して、この問題の答えを取得せよ
docker history hatena/intern-2020-docker-quizでこのイメージの履歴を表示することができます。truncate されている & 見づらいので、
docker history --no-trunc --format "{{.CreatedAt}}: {{.CreatedBy}}" hatena/intern-2020-docker-quizのようにして整形して出力します。履歴を見てみると、下記の 2 ファイルがコンテナにコピーされていることが分かります。
docker_quiz.go.enc(暗号化されたソースコードっぽい)password__delete_me_after_decrypting(復号に使うパスワードが書かれたファイルっぽい)残念ながら、
password__delete_me_after_decryptingは復号に使われたあと削除されています。
docker save hatena/intern-2020-docker-quiz > docker-quiz.tarで、イメージを tar ファイルとして出力することができます。tar の中身はレイヤの集合になっています。その中から前述の 2 ファイルがコピーされたときのレイヤを探し出せば復元することができそうです。tar ファイルを展開し、2 つのファイルがコピーされたときっぽいレイヤの layer.tar を見つけ出し、展開します。
tar tvf path/to/layer.tarで tar ファイルの中身を表示したり、ファイルサイズから推測したりすると比較的容易に見つけられます。2 つのファイルを回収することができたら、前述の
docker historyで見つかった履歴と同じコマンドでソースコードを復号します。openssl enc -d -aes-256-cbc -pbkdf2 -in docker_quiz.go.enc -out docker_quiz.go -pass file:password__delete_me_after_decryptingソースコードを見てみると、Q7 は入力した答えのバイト列のそれぞれの値に
0xffとの排他的論理和をとった値が、[0xb9, 0xb3, 0xbe, 0xb8, 0xa0, 0xce, 0xc9, 0x8a, 0xca, 0x95, 0x88, 0x9d, 0x8d]と一致するかどうかを判定しているのが分かります。この答えは逆算することができます。下記は JavaScript で逆算する例です。[0xb9, 0xb3, 0xbe, 0xb8, 0xa0, 0xce, 0xc9, 0x8a, 0xca, 0x95, 0x88, 0x9d, 0x8d].map((b) => String.fromCharCode(b ^ 0xff)).join('');手に入った文字列が答えになります。
失敗した方法
/app/docker_quizをホストにコピーし、objdump -d ./docker_quizで逆アセンブルをしてみたけど何も分からなかった。感想
Docker は普段から使っていましたが知らないことも多く、勉強になりました。楽しかったです。
- 投稿日:2020-09-13T20:39:49+09:00
Dockerのインストールと初期設定
環境
Mac OS Mojave
Dockerのインストール
まずはDockerのアカウントを作成する
こちらSign Up Todayのところに情報を入力してSign Upボタンを押す。
左下のContinue with Free をクリック
するとメールアドレスにメールが届くので
Verify email addressボタンを押す。すると以下の様な画面になるので
「Download Docker Desktop for Mac」を押す。もしくは、こちらからDockerをインストールするためのdmgをダウンロード。
あとはインストーラーの指示にしたがってインストール
Dockerのアプリケーションを起動すると右上にくじらのアイコンが出る。
コマンドラインで以下のように入力し、バージョンが表示されればインストールできている。
docker version初期設定
くじらマーク > Preferences で環境設定画面を開く。
右上のsign inボタンでsigninはしておく。
General
Start Docker Desktop when you log in をオフ
Macにログイン時にDockerを起動する必要はないため。Resources
CPUs 2
Memory: 2GB
Swap 1GB
Disk image size 16GBDisk image sizeは減らしておかないと容量がめっちゃなくなる。
それ以外は適当。減らしても良さそう。Apply & Restartボタンを押すと、やっちゃっていいですか?みたいなダイアログが出るのでOKおす。
参考
- 投稿日:2020-09-13T17:48:24+09:00
Docker (Desktop|Hub|Compose)? を体系的に学べる公式チュートリアル和訳
この記事について
この記事は、Docker Desktopのチュートリアルを和訳したものです。
公式のチュートリアルなので、安心して、かつ効率的に学習することができます。
Docker DesktopからDocker Hub、Docker Composeまで網羅されているので、初学者がDockerに初めて触れたり、中級者が基礎を振り返るのに最適です。
翻訳元のチュートリアルは、2020/09/13時点で最新のものです。長い時間が経過している場合、情報が古くなっている場合がございますのでご注意ください。
読者に誤解を与えない部分は、読みやすさを重視して適宜意訳しています。
DeepL等を使用して推敲は行っていますが、間違っているところやより良い表現があれば、編集リクエストをお願いいたします。
翻訳元
getting-started : https://github.com/docker/getting-started/tree/6190776cb618b1eb3cfb21e207eefde511d13449
Dockerデスクトップ
Dockerデスクトップは、コンテナ化されたアプリケーションとマイクロサービスを構築・共有するためのツールです。MacOSとWindows上で動作します。
和訳者メモ
Dockerデスクトップをインストールするには、ここにアクセスしてダウンロードするか、下記コマンドを実行してHomebrew Caskよりインストールしてください。
$ brew cask install dockerDockerデスクトップを開くと、チュートリアルが開始します。それぞれのコマンド等については後半で詳しく説明するので、ここではどのような流れでコンテナが作成されるのかを体感してください。
Clone
まず、レポジトリをクローンします。
Getting Started のプロジェクトは、シンプルなGithubレポジトリで、イメージを作成してコンテナとして実行するために必要なものが全て含まれています。
$ git clone https://github.com/docker/getting-started.gitBuild
次に、イメージを作成します。
Dockerイメージは、コンテナのためのプライベートなファイルシステムです。コンテナが必要とする全てのファイルとコードを提供します。
$ cd getting-started docker build -t docker101tutorial .Run
コンテナを実行しましょう。
前のステップで作成したイメージを元にしたコンテナを起動します。コンテナを起動すると、PCの他の場所から安全に隔離されたリソースを使用してアプリケーションを起動できます。
$ docker run -d -p 80:80 --name docker-tutorial docker101tutorialShare
イメージを保存して共有しましょう。
イメージをDocker Hubに保存・共有すると、他のユーザーがどんな目的のマシン上でも、簡単にイメージをダウンロードして起動できるようになります。
なお、Docker Hubを利用するには、Dockerアカウントを作成する必要があります。
$ docker tag docker101tutorial michinosuke/docker101tutorial $ docker push michinosuke/docker101tutorialDocker Tutorial
Dockerデスクトップのチュートリアルで作成したコンテナにアクセスすると、より詳しいDocker Tutorialが始まります。
http://localhostにアクセスしましょう。
はじめよう
さっき実行したコマンドについて
このチュートリアルのコンテナを立てることができました。
まず、先ほど実行したコマンドについて説明します。忘れているかもしれないので、もう一度書きます。
docker run -d -p 80:80 docker/getting-startedいくつかフラグが使用されているのに気付いたと思います。それぞれのフラグは以下のような意味があります。
-d: コンテナをデタッチモード(バックグラウンド)で実行する。
-p: ホストの80番ポートをコンテナの80番ポートにマッピングする。
docker/getting-started: 使用するイメージ応用
一文字のフラグは結合することでコマンド全体を短くできます。例えば、上のコマンドは以下のようにも書けます。
docker run -dp 80:80 docker/getting-startedDockerダッシュボード
チュートリアルを進める前に、PC上で起動しているコンテナ一覧を表示できるDockerダッシュボードを紹介します。Dockerダッシュボードを使えば、コンテナのログに素早くアクセスできたり、コンテナ内のシェルを取得できたり、コンテナのライフサイクル(停止や削除)を簡単に管理できたりします。
ダッシュボードにアクセスするには、MacまたはWindowsの手順に従ってください。今開いてみると、このチュートリアルが起動しているはずです。コンテナの名前(下では
jolly_boumanになっている)は、ランダムに生成された名前です。なので、違う名前で表示されていると思います。コンテナとは?
コンテナを起動したわけですが、コンテナとは一体なんなのでしょうか?簡単にいうと、コンテナはホストマシンにある他の全てのプロセスから隔離された、シンプルな別のプロセスです。隔離には、カーネルの名前空間とCgroups、長い間Linuxで使用されていた機能を使用します。Dockerはこれらの機能を親しみやすく使いやすいものにするために取り組んできました。
コンテナイメージとは?
コンテナを実行する際には、隔離されたファイルシステムを使用します。このファイルシステムはコンテナイメージから供給されます。イメージにはコンテナのファイルシステムが含まれるので、アプリケーションを実行するのに必要な全ての依存関係・設定・スクリプト・バイナリなどはすべてイメージに含まれている必要があります。また、イメージには、環境変数や起動時のデフォルトコマンド、その他のメタデータなどコンテナの設定も含まれています。
イメージのレイヤ、ベストプラクティスなどの詳細な内容については後で紹介します。
情報
chrootをご存知なら、コンテナはchrootの拡張版だと考えてください。ファイルシステムは、ただイメージから持ってきたものです。しかし、コンテナにはchrootより強力な分離機能が追加されています。使うアプリの紹介
ここからは、Node.jsで動くシンプルなリスト管理アプリを使って進めていきます。Node.jsが分からなくても問題ありません。JavaScriptについての知識も必要ないです。
ここでは、開発チームはとても小さく、MVP(実用最小限の製品)を示すためにシンプルなアプリを作っている状況を想定します。大規模なチームや複数の開発者などに対してどう動作するかを考える必要はなく、どうやって動くか、何ができるのかを示すためのアプリを作成します。
アプリを手に入れる
アプリケーションを動かす前に、アプリケーションのソースコードをPCに入れる必要があります。実際のプロジェクトでは、リポジトリからクローンするのが一般的だと思います。しかし、このチュートリアルでは、アプリケーションが入ったZIPファイルを作成しておいたので、そちらを使用します。
ZIPファイルをダウンロードしたら、ZIPファイルを開いて、解凍してください。
解凍したら、任意のエディターでプロジェクトを開いてください。エディターをインストールしていない場合、Visual Studio Codeを使用してください。
package.jsonと2つのサブディレクトリ(srcとspec)が表示されるはずです。アプリのコンテナイメージを作成する
アプリケーションを構築するには、
Dockerfileを使用します。Dockerfileは、コンテナイメージを作成するために使われるテキストベースの命令スクリプトです。今までにDockerfileを作成したことのある方なら、下のDockerfileには欠陥があると分かるかもしれません。それについては後で説明します。1.以下の内容を書き込んだ
Dockerfileをpackage.jsonがあるディレクトリに作成してください。FROM node:12-alpine WORKDIR /app COPY . . RUN yarn install --production CMD ["node", "src/index.js"]
Dockerfileに.txtなどの拡張子が付いていないか確認してください。エディターによっては自動的に拡張子をつけてしまい、次のステップでエラーになる場合があります。2.移動していない場合は、ターミナルを開いて
Dockerfileのあるappディレクトリ に移動します。docker buildコマンドを使用してコンテナイメージを構築しましょう。docker build -t getting-started .このコマンドは、Dockerfileを使用して、新しいコンテナイメージを構築します。たくさんの"レイヤ"がインストールされたのに気がついたと思います。なぜかというと、
node:12-alpineイメージを起点にすることをビルダーに指示したからです。しかし、そのイメージがPC上になかったので、イメージをダウンロードする必要がありました。イメージがダウンロードされた後、アプリケーションをコピーし、
yarnを使用してアプリケーションの依存関係をインストールしました。CMD命令は、このイメージからコンテナが起動されたときに実行されるデフォルトのコマンドを指定します。最後に、
-tフラグは、イメージにタグを付けます。これは、イメージに人間の理解しやすい名前をつけるものだと考えてください。ここでは、イメージにgetting-startedと名付けたので、コンテナを起動するときはこの名前を参照することができます。
docker buildコマンドの最後につけた.は、DockerがカレントディレクトリにあるDockerfileを探すことを示しています。アプリのコンテナを起動する
イメージは用意したので、アプリケーションを実行させてみましょう。それには、
docker runコマンドを使用します。(すでに一度使ったのを覚えていますか?)1.
docker runコマンドを使用してコンテナを起動し、先ほど作成したイメージの名前を指定してください。docker run -dp 3000:3000 getting-started
-dフラグと-pフラグを覚えていますか?新しいコンテナをデタッチ(バックグランド実行)モードで起動し、ホストの3000番ポートをコンテナの3000番ポートにマッピングしました。ポートマッピングをしなかった場合、アプリケーションにアクセスすることはできません。2.数秒後、http://localhost:3000をWebブラウザで開いてみてください。アプリが表示されるはずです。
3.1つか2つアイテムを追加してみて、期待通りの動作になるか確認してください。アイテムに完了のチェックを入れたり、アイテムを削除することができます。フロントエンドがアイテムをバックエンドに保存できています。とても簡単でしょう?
この時点で、いくつかのアイテムを持つTodoリスト管理アプリができました。それでは、少し変更を加えながら、コンテナの管理について学んでいきましょう。
Dockerダッシュボードを見てみると、コンテナが2つ起動しているのが分かります。(このチュートリアル自身と、起動したばかりのアプリコンテナです。)
要約
この章では、コンテナイメージの構築についての基本的なことを学び、そのためのDockerfileを作成しました。イメージを構築してから、コンテナを起動し、実行中のアプリを触ってみました。
次は、アプリに修正を加えて、実行中のアプリを新しいイメージで更新する方法を学びましょう。途中で、便利なコマンドもいくつか学びます。
アプリをアップデートする
ちょっとした機能のリクエストとして、プロダクトチームからToDoリストのアイテムが存在しないときに表示される「空のテキスト」を変更してほしいという依頼がありました。以下のように変更したいとのことです。
You have no todo items yet! Add one above!
簡単ですよね?この変更を加えていきます。
ソースコードを更新する
1.
src/static/js/app.jsの56行目を書き換えて、新しいテキストが使用されるようにします。- <p className="text-center">No items yet! Add one above!</p> + <p className="text-center">You have no todo items yet! Add one above!</p>2.先ほど使ったものと同じコマンドを使って、更新したイメージをビルドしましょう。
docker build -t getting-started .3.更新したコードを使って新しいコンテナを起動しましょう。
docker run -dp 3000:3000 getting-startedあ゛!多分こんなエラーが表示されたと思います。(IDは違います)
docker: Error response from daemon: driver failed programming external connectivity on endpoint laughing_burnell (bb242b2ca4d67eba76e79474fb36bb5125708ebdabd7f45c8eaf16caaabde9dd): Bind for 0.0.0.0:3000 failed: port is already allocated.何が起こったんでしょうか。古いコンテナが動いていたので、新しいコンテナを立ち上げることができなかったのです。この問題が起こった理由として、特定のポートをリッスンできるのはコンテナがあるPC上で一つのプロセス(コンテナを含む)だけですが、コンテナは3000番ポートを既に使っていたからです。このエラーを解消するためには、古いコンテナを削除する必要があります。
古いコンテナを置き換える
コンテナを削除するためには、まず停止させる必要があります。停止してしまえば、削除できます。古いコンテナを削除するには、2通りの方法があります。お好きな方法をお選びください。
CLIでコンテナを削除する
1.
docker psコマンドを使用してコンテナのIDを取得します。docker ps2.
docker stopコマンドを使用してコンテナを停止させます。# <the-container-id>はdocker psコマンドで取得したIDで置き換えてください。 docker stop <the-container-id>3.コンテナを停止したら、
docker rmコマンドで削除します。docker rm <the-container-id>応用
docker rmコマンドに"force"フラグを追加することで、一つのコマンドでコンテナの停止と削除を行うことができます。例)
docker rm -f <the-container-id>Dockerダッシュボードを使ってコンテナを削除する
Dockerダッシュボードを開いて、二回クリックするだけでコンテナを削除することができます。コンテナIDを探して削除するよりはるかに簡単です。
ダッシュボードを開いて、アプリのコンテナにカーソルを合わせると、アクション一覧が右側に表示されます。
ゴミ箱ボタンをクリックすると、コンテナが削除されます。
削除を確認したら完了です。
更新したアプリを起動する
1.更新したアプリを起動します。
docker run -dp 3000:3000 getting-started2.http://localhost:3000でブラウザを再読み込みすると、アップデートされたテキストが表示されます。
要約
アプリの更新ができた一方で、特筆すべきことが2点ありました。
存在していた全てのToDoリストが全て消滅してしまったことです。これではいいアプリとは言えません。これについては近いうちに話そうと思います。
小さな変更だったのにもかかわらず、多くのステップを踏む必要がありました。次の章では、変更を加えるたびに新しいコンテナの再構築と起動を行わなくてもコードを更新する方法について紹介します。
永続性についてお話しする前に、イメージを他人と共有する方法について学びましょう。
アプリを共有する
イメージを作成できたので、早速共有してみましょう。Dockerイメージを共有するには、Dockerレジストリを使用する必要があります。デフォルトのレジストリはDocker Hubであり、今まで使ってきたイメージもそこから持ってきたものでした。
レポジトリの作成
イメージをプッシュするには、まずDocker Hub上にレポジトリを作成する必要があります。
Docker Hubにアクセスして、必要ならログインしてください。
Create Repositoryボタンをクリックしてください。
レポジトリ名は
getting-startedを指定してください。公開レベルがPublicになっていることを確認してください。Createボタンをクリックしてください。
ページの右側を見ると、Dockerコマンドというのがあると思います。ここには、このレポジトリにプッシュするために実行する必要のあるコマンドの例が記載されています。
イメージをプッシュする
1.Docker Hubにあったpushコマンドをコマンドライン上で実行してみてください。注意点として、コマンドのネームスペースは"docker"ではなく、自分の名前空間を使用してください。
$ docker push docker/getting-started The push refers to repository [docker.io/docker/getting-started] An image does not exist locally with the tag: docker/getting-startedなぜ失敗してしまったのでしょうか。このpushコマンドはdocker/getting-startedという名前のイメージを探しましたが、見つからなかったのです。
docker image lsを実行してみてもイメージは見つかりません。
この問題を解決するためには、これまでに作成したイメージに別の名前をつけるため、タグ付けする必要があります。和訳者メモ
docker image lsと同じ動作をするコマンドにdocker imagesがあります。これはDockerコマンドの再編成によるもので、docker image lsの方が新しく、推奨されています。このチュートリアルでは、今後も同じ動作をするコマンドを他に持つコマンドが登場します。2.
docker login -u YOUR-USER-NAMEコマンドを使用して、Docker Hubにログインします。3.
docker tagコマンドを使用して、getting-startedイメージに新しい名前をつけます。YOUR-USER-NAMEはあなたのDocker IDに置き換えてください。docker tag getting-started YOUR-USER-NAME/getting-started4.もう一度pushコマンドを実行してみましょう。イメージ名にタグは追加していないので、Docker Hubからコピー&ペーストしてきた場合は、
tagnameの部分は削除してください。タグを指定しない場合、Dockerはlatestというタグを使用します。docker push YOUR-USER-NAME/getting-startedイメージを新しいインスタンス上で動かす
イメージを構築してレジストリにプッシュできたので、新しいインスタンス上でこのコンテナイメージを動かしてみましょう。そのために、Play with Dockerを使用します。
1.ブラウザでPlay with Dockerを開きます。
2.Docker Hubアカウントでログインします。
3.ログインしたら、左のバーにある「+ ADD NEW INSTANCE」リンクをクリックします(見当たらない場合、ブラウザを少し横に広げてください)。数秒後、ブラウザ上にターミナルウィンドウが表示されます。
4.ターミナル上で、プッシュしたアプリを起動させましょう。
docker run -dp 3000:3000 YOUR-USER-NAME/getting-startedイメージが取得されたのち、起動します。
5.3000と書かれたバッジが表示されるので、それをクリックすると、変更を加えたアプリが表示されます。やりましたね。3000と書かれたバッジが表示されない場合、「Open Port」ボタンをクリックして、3000と入力してください。
要約
この章では、イメージをレジストリにプッシュして共有する方法を学びました。それから、新しいインスタンスに入って、プッシュしたイメージを起動しました。これは、CIパイプラインでは一般的なことで、パイプラインがイメージを作成してレジストリにプッシュすると、本番環境では最新版のイメージを使用できるようになります。
ここまでは理解できたので、さっきの章の最後の話題に戻りましょう。アプリを再起動すると、ToDoリストのアイテムが全て消去されてしまうという問題がありました。当然それでは良いUX(ユーザー体験)とは言えないので、どうやってリスタートした後もデータを保持するかを学びましょう。
データベースを永続化する
気がついたと思いますが、ToDoリストはコンテナを起動するたびに初期化されています。なぜでしょうか。コンテナがどのように動作しているのかをもう少し掘り下げてみましょう。
コンテナのファイルシステム
コンテナが起動したとき、イメージの様々なレイヤがファイルシステムのために使用されます。また、それぞれのコンテナは作成/更新/削除を行うための「スクラッチスペース」を確保します。同じイメージが使われている場合でも、変更は別のコンテナに影響しません。
実際に見てみる
実際にみてみるために、2つのコンテナを起動させ、それぞれにファイルを作成しましょう。片方のコンテナでファイルを作成しても、もう一方のコンテナでそのファイルが有効で無いことがわかります。
1.1から10000までのランダムな数字を書き込んだ
/data.txtを作成するubuntuコンテナを起動します。docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"コマンドに詳しい方なら、Bashシェルを起動して、二つのコマンドを呼び出していることが分かると思います(そのために
&&を使用しています)。最初の部分で、ランダムな1つの数字を/data.txtに書き込んでいます。2つ目のコマンドは、コンテナの実行を維持するためにファイルを監視し続けているだけです。2.出力されたものを確認するために、
execでコンテナの中に入ってみましょう。ダッシュボードを開いて、起動しているubuntuイメージの最初のアクションをクリックすることで、これを行うことができます。ubuntuコンテナの中でシェルが起動しているのがわかったと思います。以下のコマンドを実行して、
/data.txtの内容を表示してみましょう。それができたら、またターミナルを閉じてください。cat /data.txt同じことをコマンドラインを使って行いたい場合は、
docker execを使用してください。docker psでコンテナのIDを取得したのち、以下のコマンドでファイルの内容を取得できます。docker exec <container-id> cat /data.txtランダムな数字が表示されるはずです。
3.別の
ubuntuコンテナを立ち上げて、同じファイルが存在しないか確認してみましょう。
docker run -it ubuntu ls /
data.txtがありません。書き込まれた先は、最初のコンテナのためのスクラッチスペースだったからです。4.
docker rm -fコマンドを使用して最初のコンテナを削除します。コンテナのボリューム
ここまでで、コンテナは起動時にイメージの定義から始めることがわかりました。コンテナはファイルの作成、更新、削除を行うことができますが、コンテナが削除されると失われ、全ての変更はそのコンテナに限定されます。しかし、ボリュームを使えば、その全てを変更することができます。
ボリュームは、コンテナの特定のファイルシステムパスがホストマシンに接続できるようにする機能を提供します。コンテナ内のディレクトリがマウントされている場合、そのディレクトリの変更はホストマシンにも影響します。コンテナを再起動しても同じディレクトリをマウントした場合、同じファイルを参照できるというわけです。
ボリュームには2通りのタイプがあります。どちらも使うのですが、とりあえずネームドボリュームを使ってみましょう。
Todoデータを永続化する
ToDoアプリはデータを
/etc/todos/todo.dbにあるSQLite Databaseに保存しています。SQLiteが分からなくても気にしないでください。SQLiteはシンプルなリレーショナルデータベースで、一つのファイルに全てのデータを保存しています。これは大きなデータを扱う上ではベストな方法ではないのですが、小さなデモアプリでは有効です。違うデータベースエンジンに切り替える方法については後述します。データベースが単一のファイルであるため、このファイルをホストで永続化して次のコンテナから参照できるようにすれば、中断した最後のところから再開できるようになるはずです。ボリュームを作成してデータが保存されているディレクトリに接続(マウントともいいます)すれば、データが永続化できます。コンテナが
todo.dbファイルに書き込むと、ボリューム内のホストに保持されます。軽く触れておくと、これから使おうとしているのは、ネームドボリュームです。ネームドボリュームはデータを入れるバケツと考えてください。Dockerはディスク上に物理的な領域を確保するので、ボリュームの名前だけ覚えておけば良いです。ボリュームを利用するときに、Dockerが正しいデータが取得されているかを検証してくれます。
1.
docker volume createコマンドでボリュームを作成します。docker volume create todo-db2.すでに立ち上げたToDoアプリは持続したボリュームを使用せずに実行されているので、ダッシュボードを使うか
doker rm -f <id>コマンドを使用して停止させてください。3.ToDoアプリのコンテナを起動するのですが、ボリューム接続を指定するのに
-vフラグを付け加えてください。ネームドボリュームを使用して/etc/todosに接続し、全てのファイルをキャプチャします。docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started4.コンテナを立ち上げたら、アプリを開いて、ToDoリストにいくつかアイテムを追加してみてください。
5.ToDoアプリのコンテナを削除します。ダッシュボードを使用するか、
docker psでIDを取得してからdocker rm -f <id>で削除を行ってください。6.上記と同じコマンドを使用して、新しいコンテナを起動してください。
7.リストが表示されることを確認したら、コンテナを削除して次に進みましょう。
データの永続化の方法を理解できましたね。
応用
ネームドボリュームとバインドマウント(これについては後で話します)は、Dockerをインストールした時からサポートされている2通りのボリュームですが、ドライバプラグインも数多く存在し、NFS、SFTP、NetAppなどをサポートしています。これは、SwarmやKubernetesなどのクラスタ環境内の複数のホスト上でコンテナを起動させたときにとても重要になります。
ボリュームについて深く知る
多くの人に「ネームドボリュームを使ったときにDockerがデータを保存する実際の場所はどこなんですか?」とよく聞かれます。知りたいのであれば、
docker volume inspectコマンドを使用すれば可能です。docker volume inspect todo-db [ { "CreatedAt": "2019-09-26T02:18:36Z", "Driver": "local", "Labels": {}, "Mountpoint": "/var/lib/docker/volumes/todo-db/_data", "Name": "todo-db", "Options": {}, "Scope": "local" } ]
Mountpointというのが、ディスク上にデータが保存されている実際の場所です。ほとんどのマシンでは、ホストからこのディレクトリにアクセスするのにRoot権限が必要になることに注意してください。Dockerデスクトップでボリュームデータに直接アクセスする
Dockerデスクトップで実行している間、Dockerコマンドは実際にはマシン上の小さな仮想マシン内で実行されています。Mountpointディレクトリの実際の内容を見たければ、まず仮想マシン内に入る必要があります。
要約
この時点で、存続したまま再起動できる機能的なアプリケーションを作ることができました。投資家に見せびらかして、私たちのビジョンを理解してもらえることを願っています。
しかしながら、変更を加えるたびにイメージを再構築するのは少し時間がかかり過ぎです。変更を加えるのにはもっと良い方法があるんです。バインドマウント(さっきほのめかしたやつ)が、その方法です。さっそく見てみましょう。
バインドマウントを使用する
前の章で、ネームドボリュームを使用して、データベースの永続化を行いました。ネームドボリュームは、データの保存場所について気にする必要がないので、単にデータを保存したい場合には有効です。
バインドマウントを使えば、ホスト上の正確なMountpointをコントロールできます。データの永続化にも使用できますが、追加のデータをコンテナに提供するのによく使われます。アプリを開発する場合、バインドマウントでソースコードをコンテナに接続して、コードを変更したり、応答したり、変更をすぐに確認したりできます。
Nodeで作られたアプリの場合、ファイルの変更を監視してアプリケーションを再起動するのにはnodemonが最適です。同じようなツールは、ほとんどの言語とフレームワークに存在します。
ボリュームのタイプ比較表
バインドマウントとネームドボリュームは、Dockerエンジンが備えた2つの主なボリュームタイプです。一方で、追加のボリュームドライバは、他のユースケース(SFTP, Ceph, NetApp, S3 など)で役立ちます。
Named Volumes バインドマウント ホストの場所 Dockerが選ぶ 自分が選ぶ マウントの例 ( -vを使用)my-volume:/usr/local/data /path/to/data:/usr/local/data コンテナのコンテンツで新しいボリュームを作成する Yes No ボリュームドライバのサポート Yes No デベロッパモードのコンテナを起動する
開発段階で使えるコンテナを起動してみましょう。以下のことを行います。
- ソースコードをコンテナにマウントする。
- "dev" dependenciesを含む全ての依存関係をインストールする。
- nodemonを起動してファイルの変更を監視する
では、始めましょう。
1.今までに使用した
getting-startedコンテナが起動していないことを確認してください。2.以下のコマンドを実行してください。何をしているかの説明もしていきます。
docker run -dp 3000:3000 \ -w /app -v "$(pwd):/app" \ node:12-alpine \ sh -c "yarn install && yarn run dev"PowerShellを使っている場合は、以下のコマンドを使用してください。
docker run -dp 3000:3000 ` -w /app -v "$(pwd):/app" ` node:12-alpine ` sh -c "yarn install && yarn run dev"
-dp 3000:3000: 今までと同じです。デタッチ(バックグラウンド)モードで起動し、ポートマッピングを作成します。
-w /app: "ワーキングディレクトリ"か、コマンドが実行されるカレントディレクトリを指定します。
-v "$(pwd):/app": コンテナのホストから、/appディレクトリにカレントディレクトリをバインドマウントします。
node:12-alpine: 使用するイメージです。このイメージはDockerfileで指定した通り、アプリのベースイメージとなっていることに気をつけてください。
sh -c "yarn install && yarn run dev": コマンドです。sh(alpineにはbashがありません)を使用してシェルを起動し、yarn installで全ての依存関係をインストールしてから、yarn run devを実行しています。package.jsonを見てみると、devスクリプトはnodemonを起動することがわかります。3.
docker logs -f <container-id>コマンドでログを見ることができます。これを見れば、準備ができていることが分かります。docker logs -f <container-id> $ nodemon src/index.js [nodemon] 1.19.2 [nodemon] to restart at any time, enter `rs` [nodemon] watching dir(s): *.* [nodemon] starting `node src/index.js` Using sqlite database at /etc/todos/todo.db Listening on port 3000ログを見終わったら、
Ctrl+Cで終了することができます。4.では、アプリに変更を加えてみましょう。
src/static/js/app.jsファイル内の「Add Item」ボタンを「Add」に変更してみましょう。109行目にあります。- {submitting ? 'Adding...' : 'Add Item'} + {submitting ? 'Adding...' : 'Add'}5.ページをリフレッシュ(もしくは開く)だけで、ブラウザにほとんど即座に変更が反映されているのがわかるはずです。Nodeサーバーを再起動するのには数秒かかるので、エラーになった場合は、数秒後にリフレッシュしてみてください。
6.他にも変更を加えてみてください。それが終わったら、コンテナを停止してから、
docker build -t getting-started .を使用して新しいイメージをビルドしてください。バインドマウントを使用することは、ローカル開発においてとても一般的なことです。その利点として、開発マシンにビルドツールや環境がインストールされている必要がないことがあります。
docker runコマンドだけで、開発環境はプルされ、準備が完了します。のちの章でDocker Composeについて話す予定ですが、これはたくさんのフラグがついたコマンドをシンプルにすることができます。要約
データベースを永続化し、投資家と創設者の要求と要望に迅速に対応できるようになりました。でも、ちょっと待ってください。素晴らしいニュースが飛び込んできました!
あなたのプロジェクトは、将来的に開発されることになりました。
製品化に備えて、データベースをSQLiteより拡張性の高いものに移行する必要があります。単純に考えて、リレーショナルデータベースはそのままに、MySQLを使用するべきでしょう。しかし、どうやってMySQLを動かせば良いのでしょうか?どうやってコンテナ間での通信を許可すれば良いのでしょうか?それについて、次の章で話していこうと思います。
複数のコンテナを持つアプリ
ここまで、一つのコンテナのアプリで作業してきました。しかし、アプリケーションにMySQLを追加したいです。「MySQLはどこで動かせば良いんですか?同じコンテナに動かして別々に起動すれば良いですか?」という質問がよくあります。一般的に、各コンテナは一つのことのみを行うべきです。それには、いくつか理由があります。
APIやフロントエンドをデータベースと異なる方法で拡張させる可能性が高い。
コンテナを分離することで、バージョンの更新を分離して行うことができます。
ローカルのデータベース用コンテナを使用することもできますが、本番環境ではデータベースを管理するサービスを使用したいと思うかもしれません。その場合、アプリと一緒にデータベースエンジンを製品に含める必要はありません。
複数のプロセスを実行するには、プロセスマネージャ(コンテナは1つのプロセスしか起動しません)が必要になり、コンテナの起動/停止が複雑になります。
もっと他にも理由はあります。なので、こんなふうに動作するようにアプリをアップデートしていきます。
コンテナのネットワーク
思い出して欲しいのですが、コンテナはデフォルトで独立して動作し、同じマシン上の他のプロセスやコンテナについて何も知りません。では、どうやってコンテナが他のコンテナと通信できるようにすれば良いのでしょうか?その答えがネットワークです。あなたがネットワークエンジニアである必要はありません。このルールだけ覚えておいてください。
2つのコンテナが同じネットワーク内にあるとき、お互いに通信することができます。同じネットワーク内にないときは、通信できません。
MySQLを起動する
コンテナをネットワーク上に配置するには2通りの方法があります。1つ目は、スタート時に割り当てる方法。2つ目は、すでにあるコンテナを接続する方法です。今回は、最初にネットワークを作成してから、起動したMySQLコンテナを接続しましょう。
1.ネットワークを作成します。
docker network create todo-app2.MySQLコンテナを起動して、ネットワークに接続します。また、データベースの初期化に使用する環境変数をいくつか定義します。(MySQL Docker Hub listingの"Environment Variables"という章を参照してください)
docker run -d \ --network todo-app --network-alias mysql \ -v todo-mysql-data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=secret \ -e MYSQL_DATABASE=todos \ mysql:5.7PowerShellを使用している場合は、以下のコマンドを使用してください。
docker run -d ` --network todo-app --network-alias mysql ` -v todo-mysql-data:/var/lib/mysql ` -e MYSQL_ROOT_PASSWORD=secret ` -e MYSQL_DATABASE=todos ` mysql:5.7
--network-aliasフラグを指定しました。これについては後述します。プロのための情報
todo-mysql-dataという名前のボリュームを使用し、それをMySQLのデータが保存される/var/lib/mysqlにマウントしました。 しかし、docker volume createコマンドは使用していません。Dockerはネームドボリュームを使おうとしていることを認識して、自動でボリュームを作成してくれたのです。3.データベースが起動しているか確認するために、データベースに接続して接続されているか確認します。
docker exec -it <mysql-container-id> mysql -pもしパスワードを聞かれたら、secretと入力してください。MySQLシェル内で、データベース一覧を表示し、
todosデータベースがあることを確認してください。mysql> SHOW DATABASES;このように表示されるはずです。
+--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | | todos | +--------------------+ 5 rows in set (0.00 sec)
todosデータベースを用意することができました。MySQLに接続する
MySQLが起動したことは確認できたので、実際に使ってみましょう。でも、同じネットワークで別のコンテナを起動したとして、どうやってコンテナを探せば良いのでしょうか。(各コンテナはそれぞれ別のIPアドレスを持っていることを覚えておいてください。)
これを理解するために、ネットワークに関する問題のトラブルシューティングやデバッグに便利なツールが入ったnicolaka/netshootコンテナを利用しましょう。
1.nicolaka/netshootイメージを使用して、新しいコンテナを立ち上げましょう。同じネットワークに接続されていることを確認してください。
docker run -it --network todo-app nicolaka/netshoot2.コンテナ内で便利なDNSツールである
digコマンドを使用します。ホスト名がmysqlのIPアドレスを探しましょう。dig mysqlすると、以下のように表示されるはずです。
; <<>> DiG 9.14.1 <<>> mysql ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32162 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;mysql. IN A ;; ANSWER SECTION: mysql. 600 IN A 172.23.0.2 ;; Query time: 0 msec ;; SERVER: 127.0.0.11#53(127.0.0.11) ;; WHEN: Tue Oct 01 23:47:24 UTC 2019 ;; MSG SIZE rcvd: 44「ANSWER SECTION」というところを見ると、
mysqlのAレコードが172.23.0.2とわかります(あなたのIPアドレスは違う値になっている可能性が高いです)。mysqlは通常有効なホスト名ではありませんが、Dockerはmysqlというネットワークエイリアスを持つコンテナのIPアドレスを解決することができました(--network-aliasフラグを使用したのを覚えていますか?)。これが意味するのは、ToDoアプリについても
mysqlという名前のホストに接続するだけで、データベースと通信できるということです。これほど簡単なことはありません。MySQLを使ったアプリを動かす
ToDoアプリでは、MySQLコネクション設定を指定するいくつかの環境変数を設定することができます。詳細は以下の通りです。
MYSQL_HOST: 稼働中のMySQLサーバのホスト名です。
MYSQL_USER: 接続に使用するユーザ名です。
MYSQL_PASSWORD: 接続に使用するパスワードです。
MYSQL_DB: 接続後に使用するデータベースです。注意
開発環境でコネクションの設定に環境変数を使うことは問題ありませんが、本番環境で動いているアプリケーションで使用することはとても推奨されない行為です。Dockerで以前セキュリティを担当していたDiogo Monicaさんがその理由を説明する素晴らしい記事を書いてくれています。
もっと安全な手法としては、コンテナのオーケストレーションフレームワークによって提供されるシークレットサポートを利用することがあります。ほとんどの場合、これらのシークレットファイルは稼働しているコンテナにマウントされます。多くのアプリ(MySQLイメージとToDoアプリを含めて)はファイルを含むファイルを指す
_FILE接尾辞がついた環境変数もサポートしています。例として、変数
MYSQL_PASSWORD_FILEを設定すると、アプリは参照されたファイルの内容をコネクションパスワードとして使用します。なお、Dockerはこれらの環境変数を一切サポートしません。アプリは、環境変数を探して、ファイルの内容を取得する方法を知っておく必要があります。説明は終わったので、コンテナを起動させましょう。
1.前述した環境変数をそれぞれ指定して、コンテナがアプリのネットワークに接続できるようにしましょう。
docker run -dp 3000:3000 \ -w /app -v "$(pwd):/app" \ --network todo-app \ -e MYSQL_HOST=mysql \ -e MYSQL_USER=root \ -e MYSQL_PASSWORD=secret \ -e MYSQL_DB=todos \ node:12-alpine \ sh -c "yarn install && yarn run dev"PowerShellを使用している場合は、以下のコマンドを使用してください。
docker run -dp 3000:3000 ` -w /app -v "$(pwd):/app" ` --network todo-app ` -e MYSQL_HOST=mysql ` -e MYSQL_USER=root ` -e MYSQL_PASSWORD=secret ` -e MYSQL_DB=todos ` node:12-alpine ` sh -c "yarn install && yarn run dev"2.コンテナのログを見ると(
docker logs <container-id>)、MySQLデータベースを使用していることを示すメッセージがあります。# Previous log messages omitted $ nodemon src/index.js [nodemon] 1.19.2 [nodemon] to restart at any time, enter `rs` [nodemon] watching dir(s): *.* [nodemon] starting `node src/index.js` Connected to mysql db at host mysql Listening on port 30003.ブラウザでアプリを開いて、ToDoリストにいくつかアプリを追加してみてください。
4.MySQLデータベースに接続して、アイテムが書き込まれているか確認しましょう。パスワードはsecretです。
docker exec -ti <mysql-container-id> mysql -p todosそしてMySQLシェル内で、以下を実行してください。
mysql> select * from todo_items; +--------------------------------------+--------------------+-----------+ | id | name | completed | +--------------------------------------+--------------------+-----------+ | c906ff08-60e6-44e6-8f49-ed56a0853e85 | Do amazing things! | 0 | | 2912a79e-8486-4bc3-a4c5-460793a575ab | Be awesome! | 0 | +--------------------------------------+--------------------+-----------+もちろん、あなたのアイテムが含まれているので、テーブルの内容は異なります。でも、ここにアイテムが保存されているのが分かりましたね。
Dockerダッシュボードを見ると、2つのコンテナが起動しています。しかし、一つのアプリにグループ化されているという表示はありません。これを改善する方法について見ていきましょう。
要約
独立したコンテナで動く外部のデータベースにデータを保存するアプリケーションを作ることができました。コンテナのネットワークについて少し学び、DNSを使用してサービスの発見を行う方法を理解しました。
しかし、このアプリを立ち上げるために必要な全てのことに、圧倒されているかもしれません。ネットワークを作成し、コンテナを起動し、全ての環境変数を指定し、ポートを解放したりする必要があります。覚えることが多すぎて誰かに伝えるのが難しくなっているのは確かです。
次の章では、Docker Composeについてお話しします。Docker Composeを使えば、アプリケーションスタックをより簡単に共有して、一つのシンプルなコマンドだけで起動するようにできます。
Docker Composeを使用する
Docker Composeは、マルチコンテナアプリの定義と共有をしやすくするために開発されたものです。Composeを使えば、YAMLファイルを作成することでサービスを定義して、1つのコマンドで起動したり、停止したりすることができます。
Composeを使用する大きな利点は、ファイルにアプリケーションスタックを定義し、(バージョン管理されている)プロジェクトリポジトリのルートに保存して、誰でも簡単にプロジェクトにコントリビュートできるようにできることです。実際、GitHubやGitLabにはそのようなプロジェクトがたくさんあります。
では、早速初めていきましょう。
Docker Composeをインストールする
Dockerデスクトップ/ツールボックスをWindowsかMacにインストールしているなら、すでにDocker Composeはインストールされています。また、Play-with-Dockerのインスタンスにも、Docker Composeはインストールされています。Linuxマシンをお使いなら、こちらのページに従ってDocker Composeをインストールしていただく必要があります。
インストールが完了したら、下記のコマンドを実行して、バージョン情報を確認できるはずです。
docker-compose versionComposeファイルを作成する
1.アプリのプロジェクトのルートに、
docker-compose.ymlというファイルを作成します。2.Composeファイルでは、スキーマバージョンを定義するところから始めます。ほとんどの場合、最新版を使うのが良いです。最新のスキーマバージョンと互換性については、Composeファイルのレファレンスを参照してください。
version: 3.73.次に、アプリの一部として動かしたいサービス(もしくはコンテナ)のリストを定義します。
version: "3.7" services:次は、サービスをComposeファイルに移行していきましょう。
アプリのサービスを定義する
思い出していただきたいのですが、以下はアプリのコンテナを定義するのに使用したコマンドです。
docker run -dp 3000:3000 \ -w /app -v "$(pwd):/app" \ --network todo-app \ -e MYSQL_HOST=mysql \ -e MYSQL_USER=root \ -e MYSQL_PASSWORD=secret \ -e MYSQL_DB=todos \ node:12-alpine \ sh -c "yarn install && yarn run dev"PowerShellを使用している場合は、以下のようなコマンドを使用しました。
docker run -dp 3000:3000 ` -w /app -v "$(pwd):/app" ` --network todo-app ` -e MYSQL_HOST=mysql ` -e MYSQL_USER=root ` -e MYSQL_PASSWORD=secret ` -e MYSQL_DB=todos ` node:12-alpine ` sh -c "yarn install && yarn run dev"1.最初に、コンテナのためのサービスエントリとイメージを定義します。サービス名は任意のものを選ぶことができます。名前は自動的にネットワークエイリアスとして使用されるので、MySQLサービスを定義するのが楽になります。
version: "3.7" services: app: image: node:12-alpine2.一般的に、commandは
imageの定義の近くに書きますが、順序は自由です。では、ファイルに書き込みましょう。version: "3.7" services: app: image: node:12-alpine command: sh -c "yarn install && yarn run dev"3.コマンドの
-p 3000:3000という部分をportsに移行しましょう。ここでは簡略した書き方を使用しますが、冗長で長い書き方も同じように使用できます。version: "3.7" services: app: image: node:12-alpine command: sh -c "yarn install && yarn run dev" ports: - 3000:30004.次に、ワーキングディレクトリ(
-w /app)とボリュームマッピング(-v "$(pwd):/app")をworking_dirとvolumesに移行します。Volumesの書き方にも短いのと長いのがあります。Docker Composeのボリューム定義の長所として、カレントディレクトリからの相対パスが使用できることがあります。
version: "3.7" services: app: image: node:12-alpine command: sh -c "yarn install && yarn run dev" ports: - 3000:3000 working_dir: /app volumes: - ./:/app5.最後に、環境変数を
environmentキーを使って移行します。version: "3.7" services: app: image: node:12-alpine command: sh -c "yarn install && yarn run dev" ports: - 3000:3000 working_dir: /app volumes: - ./:/app environment: MYSQL_HOST: mysql MYSQL_USER: root MYSQL_PASSWORD: secret MYSQL_DB: todosMySQLサービスを定義する
では、MySQLサービスを定義します。コンテナのために使用したコマンドは以下の通りです。
docker run -d \ --network todo-app --network-alias mysql \ -v todo-mysql-data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=secret \ -e MYSQL_DATABASE=todos \ mysql:5.7PowerShellを使用している場合は、以下のコマンドを使用してください。
docker run -d ` --network todo-app --network-alias mysql ` -v todo-mysql-data:/var/lib/mysql ` -e MYSQL_ROOT_PASSWORD=secret ` -e MYSQL_DATABASE=todos ` mysql:5.71.まず新しいサービスを定義して、
mysqlと名付けると、自動でネットワークエイリアスを取得します。使用するイメージを指定しましょう。version: "3.7" services: app: # The app service definition mysql: image: mysql:5.72.次に、ボリュームマッピングを指定します。
docker runでコンテナを起動したとき、ネームドボリュームが自動で作成されました。しかし、Composeを使用して起動したときには作成されません。トップレベルにあるvolume:でボリュームを定義してから、サービスコンフィグのマウントポイントを指定します。ボリューム名だけを指定すると、デフォルトのオプションが使用されます。ですが、他にも多くのオプションが利用可能です。version: "3.7" services: app: # The app service definition mysql: image: mysql:5.7 volumes: - todo-mysql-data:/var/lib/mysql volumes: todo-mysql-data:3.最後に、環境変数を指定します。
version: "3.7" services: app: # The app service definition mysql: image: mysql:5.7 volumes: - todo-mysql-data:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: secret MYSQL_DATABASE: todos volumes: todo-mysql-data:完成した
docker-compose.ymlは以下のようになります。version: "3.7" services: app: image: node:12-alpine command: sh -c "yarn install && yarn run dev" ports: - 3000:3000 working_dir: /app volumes: - ./:/app environment: MYSQL_HOST: mysql MYSQL_USER: root MYSQL_PASSWORD: secret MYSQL_DB: todos mysql: image: mysql:5.7 volumes: - todo-mysql-data:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: secret MYSQL_DATABASE: todos volumes: todo-mysql-data:アプリケーションスタックを起動する
docker-compose.ymlが用意できたので、あとは起動するだけです。1.まず、他にapp/dbのコピーが実行されていないことを確認してください。(
docker psとdocker rm -f <ids>を使用してください。)2.
docker-compose upコマンドを使用して、アプリケーションスタックを起動してください。-dフラグを追加して、バックグラウンドで実行されるようにします。docker-compose up -d実行すると、以下のように出力されるはずです。
Creating network "app_default" with the default driver Creating volume "app_todo-mysql-data" with default driver Creating app_app_1 ... done Creating app_mysql_1 ... doneネットワークと同じくボリュームも作成することができていますね。デフォルトでは、Docker Composeはアプリケーションスタックのネットワークを自動で作成します(Composeファイルで定義しなかったのはこのためです)。
和訳者メモ -
docker-compose upで以下のエラーが表示された場合[ERROR] [FATAL] InnoDB: Table flags are 0 in the data dictionary but the flags in file ./ibdata1 are 0x4800!以下のコマンドでボリューム削除すると直りました。最初に間違えて
mysql:latestでdocker-compose.ymlを書いたのが原因だったかもしれないです。docker volume rm app_todo-mysql-data3.
docker-compose logs -fコマンドを使用してログを見て見ましょう。各サービスのログが一つに集約されているのが分かると思います。これは、タイミングに関する問題を確認したいときにとても便利です。-fフラグはログを追従するので、生成されたログをライブ出力します。実際の出力は以下のようになります。
mysql_1 | 2019-10-03T03:07:16.083639Z 0 [Note] mysqld: ready for connections. mysql_1 | Version: '5.7.27' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL) app_1 | Connected to mysql db at host mysql app_1 | Listening on port 3000最初のラインの(色付けされている)サービス名は、メッセージを識別するのに役立ちます。特定のサービスについてのログを見たいときは、ログコマンドの末尾にサービス名を追加してください(例:
docker-compose logs -f app)。応用 - アプリを起動する前にデータベースを待機させる
実際には、アプリケーションを起動しても、MySQLが起動して準備が整ってから接続を試みます。Dockerには、コンテナが完全に起動、実行、準備されてから別のコンテナを起動するための組み込みのサポートは用意されていません。Nodeベースのプロジェクトでは、wait-portを使用できます。同じようなプロジェクトは、他の言語・フレームワークにおいても存在します。
4.アプリを開くと、起動しているのを確認できるはずです。そして、停止も一つのコマンドで行えます。
Dockerダッシュボードでアプリケーションスタックを確認する
Dockerダッシュボードを見ると、appという名前のグループがあります。これはDocker Composeのプロジェクト名で、コンテナをグループ化するのに使用されています。デフォルトでは、プロジェクト名は
docker-compose.ymlが配置されているディレクトリの名前です。appのドロップダウンを開くと、composeファイルで定義した2つのコンテナが確認できます。そちらの名前は
<プロジェクト名>_<サービス名>_<複製番号>というふうになっています。そのおかげで、どのコンテナがどのアプリか、どのコンテナがmysqlデータベースかということが分かりやすくなっています。全て停止する
docker-compose downを実行するか、Dockerダッシュボードでapp全体をゴミ箱に入れるだけで、全て停止できます。コンテナは停止され、ネットワークは削除されます。ボリュームの削除
デフォルトでは、
docker-compose downを実行しても、composeファイル内のネームドボリュームは削除されません。ボリュームを削除したい場合は、--volumesフラグを付け加える必要があります。Dockerダッシュボードでは、アプリスタックを削除してもボリュームは削除されません。
停止したら、他のプロジェクトに切り替えて
docker-compose upを実行するだけで、そのプロジェクトを開発することができます。とても簡単ですよね。要約
この章では、Docker Composeについてと、それによって複数サービスのアプリケーションの定義と共有がどれだけ簡単になるのかということを学びました。使っていたコマンドを適切なCompose形式に移行してComposeファイルを作成しました。
チュートリアルも終盤に入りました。しかし、これまで使ってきたDockerfileには大きな問題があるので、イメージ構築に関するベストプラクティスをいくつか取り上げていきたいと思います。では、みてみましょう。
イメージ構築のベストプラクティス
イメージのレイヤ
イメージの構成要素を確認できることをご存知ですか?
docker image historyコマンドを使用すれば、イメージに含まれる各レイヤの作成に使用されたコマンドを確認することができます。1.
docker image historyコマンドを使用して、このチュートリアルで以前作成したgetting-startedイメージのレイヤを見てみましょう。docker image history getting-startedすると、以下のように表示されるはずです(おそらくIDは異なります。)。
IMAGE CREATED CREATED BY SIZE COMMENT a78a40cbf866 18 seconds ago /bin/sh -c #(nop) CMD ["node" "src/index.j… 0B f1d1808565d6 19 seconds ago /bin/sh -c yarn install --production 85.4MB a2c054d14948 36 seconds ago /bin/sh -c #(nop) COPY dir:5dc710ad87c789593… 198kB 9577ae713121 37 seconds ago /bin/sh -c #(nop) WORKDIR /app 0B b95baba1cfdb 13 days ago /bin/sh -c #(nop) CMD ["node"] 0B <missing> 13 days ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B <missing> 13 days ago /bin/sh -c #(nop) COPY file:238737301d473041… 116B <missing> 13 days ago /bin/sh -c apk add --no-cache --virtual .bui… 5.35MB <missing> 13 days ago /bin/sh -c #(nop) ENV YARN_VERSION=1.21.1 0B <missing> 13 days ago /bin/sh -c addgroup -g 1000 node && addu… 74.3MB <missing> 13 days ago /bin/sh -c #(nop) ENV NODE_VERSION=12.14.1 0B <missing> 13 days ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B <missing> 13 days ago /bin/sh -c #(nop) ADD file:e69d441d729412d24… 5.59MBそれぞれにレイヤがイメージのレイヤを表しています。この画面では、イメージのベースが下部、最新のレイヤーが上部に表示されています。これを使えば、素早くそれぞれのレイヤを確認し、サイズの大きいイメージを突き止めるのに役立ちます。
2.いくつかの行が省略されていることに気がついたでしょうか。
--no-truncフラグを付け加えると、フル出力を得ることができます。(省略されたフラグを使って省略されていない出力を得るって、面白いですよね。)docker image history --no-trunc getting-startedレイヤーのキャッシュ
実際にレイヤーを見たわけですが、これはコンテナイメージのビルド時間を減らすことに関してとても重要な話です。
レイヤーを変更したら、下流の全てのレイヤーを再生成する必要があります。
使用していたDockerfileをもう一度見てみましょう。
FROM node:12-alpine WORKDIR /app COPY . . RUN yarn install --production CMD ["node", "src/index.js"]image historyの出力の話に戻ると、Dockerfileのそれぞれのコマンドがイメージの新しいレイヤーになっていることが分かります。イメージに変更を加えたとき、yarnの依存関係が再インストールされたことを覚えているかもしれません。これを修正する方法はあるのでしょうか。ビルドするたびに同じ依存関係をインストールするなんて馬鹿げていますよね。
これを修正するには、Dockerfileを再構築して依存関係をキャッシュする必要があります。Nodeベースのアプリケーションでは、
package.jsonファイルで依存関係が定義されています。ということは、このファイルだけを最初にコピーしておけば、依存関係がインストールされてから他のファイルがコピーされます。なので、package.jsonを変更したときのみyarnの依存関係が再作成されるようになります。素晴らしいでしょう?1.Dockerfileを更新して、最初に
package.jsonのコピーが行われ、依存関係がインストールされ、それから他のファイルがコピーされるようにします。FROM node:12-alpine WORKDIR /app COPY package.json yarn.lock ./ RUN yarn install --production COPY . . CMD ["node", "src/index.js"]2.Dockerfileと同じフォルダに
.dockerignoreというファイルを作成し、以下の内容を書き込みます。node_modules
.dockerignoreファイルを使えば、イメージに必要なファイルだけを選択してコピーすることができます。詳しくはここを参照してください。この場合、node_modulesフォルダは2回目のCOPYで除外されます。さもなければ、RUNのコマンドで生成されたファイルで上書きされるでしょう。なぜこの方法がNode.jsアプリケーションで推奨されているかということと、他のベストプラクティスについての詳細は、DockerでのNode製Webアプリをご覧ください。3.
docker buildで新しいイメージをビルドします。docker build -t getting-started .以下のように表示されるはずです。
Sending build context to Docker daemon 219.1kB Step 1/6 : FROM node:12-alpine ---> b0dc3a5e5e9e Step 2/6 : WORKDIR /app ---> Using cache ---> 9577ae713121 Step 3/6 : COPY package.json yarn.lock ./ ---> bd5306f49fc8 Step 4/6 : RUN yarn install --production ---> Running in d53a06c9e4c2 yarn install v1.17.3 [1/4] Resolving packages... [2/4] Fetching packages... info fsevents@1.2.9: The platform "linux" is incompatible with this module. info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation. [3/4] Linking dependencies... [4/4] Building fresh packages... Done in 10.89s. Removing intermediate container d53a06c9e4c2 ---> 4e68fbc2d704 Step 5/6 : COPY . . ---> a239a11f68d8 Step 6/6 : CMD ["node", "src/index.js"] ---> Running in 49999f68df8f Removing intermediate container 49999f68df8f ---> e709c03bc597 Successfully built e709c03bc597 Successfully tagged getting-started:latest全てのレイヤーが再構築されているのがわかると思います。Dockerfileを大きく変更したので、もう問題ありません。
4.
src/static/index.htmlファイルを編集しましょう(<title>のところの内容を"The Awesome Todo App"に変更します)。5.もう一度
docker build -t getting-started .でDockerイメージをビルドします。今回は、表示される内容が少し変わって、以下のようになるはずです。Sending build context to Docker daemon 219.1kB Step 1/6 : FROM node:12-alpine ---> b0dc3a5e5e9e Step 2/6 : WORKDIR /app ---> Using cache ---> 9577ae713121 Step 3/6 : COPY package.json yarn.lock ./ ---> Using cache ---> bd5306f49fc8 Step 4/6 : RUN yarn install --production ---> Using cache ---> 4e68fbc2d704 Step 5/6 : COPY . . ---> cccde25a3d9a Step 6/6 : CMD ["node", "src/index.js"] ---> Running in 2be75662c150 Removing intermediate container 2be75662c150 ---> 458e5c6f080c Successfully built 458e5c6f080c Successfully tagged getting-started:latestビルドがとても早くなったのに気がついたでしょう。また、ステップ1-4には全て
Using casheが含まれているのが分かると思います。これで、ビルドキャッシュを使うことができました。イメージをプッシュし、プルし、更新するのがとても早くなります。マルチステージビルド
このチュートリアルではあまり深く触れませんが、マルチステージビルドは、複数のステージを使用してイメージを作成するのに役立つとんでもなくパワフルなツールです。以下のような利点があります。
ランタイムの依存関係とビルド時間の依存関係を分離できます。
アプリが起動するのに必要な分だけを配置することで、イメージ全体のサイズを減らします。
MavenとTomcatの例
Javaベースのアプリケーションを構築する際には、ソースコードをJavaバイトコードにコンパイルするのにJDKが必要になります。しかし、JDKは本番環境ではそのJDKは必要ありません。また、アプリをビルドするのにMavenやGradleのようなツールを使うかもしれません。これらも最終的なイメージには必要ありません。ここで、マルチステージビルドが役立ちます。
FROM maven AS build WORKDIR /app COPY . . RUN mvn package FROM tomcat COPY --from=build /app/target/file.war /usr/local/tomcat/webappsこの例では、
buildと名付けた1つ目のステージで、Mavenを用いたJavaのビルドを行います。FROM tomcatから始まる2つ目のステージで、buildステージからファイルをコピーします。最終的なイメージは、作成される最終ステージ(--targetフラグを使用するとオーバーライドされます)だけです。Reactの例
Reactアプリケーションをビルドするときは、JSコード(通常はJSX)、SASSスタイルシートなどを静的なHTMLとJS、CSSにコンパイルするNode環境が必要がです。サーバーサイドレンダリングが必要ない場合は、本番環境のビルドにNode環境は必要ありません。それなら、静的なNginxコンテナに静的なリソースを配置すればいいだけの話ですよね。
FROM node:12 AS build WORKDIR /app COPY package* yarn.lock ./ RUN yarn install COPY public ./public COPY src ./src RUN yarn run build FROM nginx:alpine COPY --from=build /app/build /usr/share/nginx/htmlここでは、
node:12イメージを使用してビルド(レイヤキャッシュの最大化)を行ってから出力をNginxコンテナにコピーしています。この方がいいでしょ?要約
イメージが構造化される方法を少し理解したことで、イメージのビルドは早くなり、より少ない変更を配置できるようになりました。また、マルチステージビルドは、イメージ全体のサイズを減らし、ビルドタイムの依存関係とランタイムの依存関係を切り離すことで最終的なコンテナのセキュリティを向上させるのに役立ちます。
次にすること
チュートリアルは終わったわけですが、コンテナについて学ぶことはもっとあります。ここで深く触れることはないですが、少しみてみましょう。
コンテナのオーケストレーション
本番環境でコンテナを動かすのは難しいことです。マシンにログインして
docker runやdocker-compose upを実行したいとは思いません。なぜでしょうか?では、コンテナが死んだら何が起こるでしょうか?複数のマシンにまたがってスケールする方法は?コンテナのオーケストレーションはこの問題を解決します。Kubernetes、Swarm、Nomad、ECSのようなツールは全てこの問題をわずかに異なる方法で解決します。予期される状態を受け取る「マネージャ」を持つというのが一般的な考えです。この状態というのは、「Webアプリのインスタンスを2つ起動して、80番ポートを開放してほしい」というようなものです。マネージャはクラスタ内の全てのマシンを監視し、作業を「ワーカ」に委任します。マネージャは(コンテナが停止された、のような)変更を監視し、それから実際の状態が予期された状態を反映するように動作します。
クラウドネイティブコンピューティング基盤プロジェクト
CNCF(クラウドネイティブ基盤のプロジェクト)は、ベンダーに依存しない様々なオープンソースプロジェクトで、Kubernetes、Prometheus、Envoy、Linkerd、NATSなどを含みます。作られたプロジェクトはこちら、全体的なCNCFの図はこちらで見ることができます。これらのたくさんのプロジェクトは、モニタリング、ロギング、セキュリティ、イメージレジストリ、メッセージングなどの問題を解決するのに役立ちます。
なので、コンテナの全容とクラウドネイティブアプリケーションの開発がよくわからない場合は、ぜひご利用ください。コミュニティに参加し、質問をして、勉強してみてください。あなたが参加してくれることを心待ちにしております。
- 投稿日:2020-09-13T17:48:24+09:00
Dockerを体系的に学べる公式チュートリアル和訳
この記事について
この記事は、Docker Desktopのチュートリアルを和訳したものです。
公式のチュートリアルなので、安心して、かつ効率的に学習することができます。
Docker DesktopからDocker Hub、Docker Composeまで網羅されているので、初学者がDockerに初めて触れたり、中級者が基礎を振り返るのに最適です。
翻訳元のチュートリアルは、2020/09/13時点で最新のものです。長い時間が経過している場合、情報が古くなっている場合がございますのでご注意ください。
読者に誤解を与えない部分は、読みやすさを重視して適宜意訳しています。
DeepL等を使用して推敲は行っていますが、間違っているところやより良い表現があれば、編集リクエストをお願いいたします。
翻訳元
getting-started : https://github.com/docker/getting-started/tree/6190776cb618b1eb3cfb21e207eefde511d13449
Dockerデスクトップ
Dockerデスクトップは、コンテナ化されたアプリケーションとマイクロサービスを構築・共有するためのツールです。MacOSとWindows上で動作します。
和訳者メモ
Dockerデスクトップをインストールするには、ここにアクセスしてダウンロードするか、下記コマンドを実行してHomebrew Caskよりインストールしてください。
$ brew cask install dockerDockerデスクトップを開くと、チュートリアルが開始します。それぞれのコマンド等については後半で詳しく説明するので、ここではどのような流れでコンテナが作成されるのかを体感してください。
Clone
まず、レポジトリをクローンします。
Getting Started のプロジェクトは、シンプルなGithubレポジトリで、イメージを作成してコンテナとして実行するために必要なものが全て含まれています。
$ git clone https://github.com/docker/getting-started.gitBuild
次に、イメージを作成します。
Dockerイメージは、コンテナのためのプライベートなファイルシステムです。コンテナが必要とする全てのファイルとコードを提供します。
$ cd getting-started $ docker build -t docker101tutorial .Run
コンテナを実行しましょう。
前のステップで作成したイメージを元にしたコンテナを起動します。コンテナを起動すると、PCの他の場所から安全に隔離されたリソースを使用してアプリケーションを起動できます。
$ docker run -d -p 80:80 --name docker-tutorial docker101tutorialShare
イメージを保存して共有しましょう。
イメージをDocker Hubに保存・共有すると、他のユーザーがどんな目的のマシン上でも、簡単にイメージをダウンロードして起動できるようになります。
なお、Docker Hubを利用するには、Dockerアカウントを作成する必要があります。
$ docker tag docker101tutorial michinosuke/docker101tutorial $ docker push michinosuke/docker101tutorialDocker Tutorial
Dockerデスクトップのチュートリアルで作成したコンテナにアクセスすると、より詳しいDocker Tutorialが始まります。
http://localhostにアクセスしましょう。
はじめよう
さっき実行したコマンドについて
このチュートリアルのコンテナを立てることができました。
まず、先ほど実行したコマンドについて説明します。忘れているかもしれないので、もう一度書きます。
docker run -d -p 80:80 docker/getting-startedいくつかフラグが使用されているのに気付いたと思います。それぞれのフラグは以下のような意味があります。
-d: コンテナをデタッチモード(バックグラウンド)で実行する。
-p: ホストの80番ポートをコンテナの80番ポートにマッピングする。
docker/getting-started: 使用するイメージ応用
一文字のフラグは結合することでコマンド全体を短くできます。例えば、上のコマンドは以下のようにも書けます。
docker run -dp 80:80 docker/getting-startedDockerダッシュボード
チュートリアルを進める前に、PC上で起動しているコンテナ一覧を表示できるDockerダッシュボードを紹介します。Dockerダッシュボードを使えば、コンテナのログに素早くアクセスできたり、コンテナ内のシェルを取得できたり、コンテナのライフサイクル(停止や削除)を簡単に管理できたりします。
ダッシュボードにアクセスするには、MacまたはWindowsの手順に従ってください。今開いてみると、このチュートリアルが起動しているはずです。コンテナの名前(下では
jolly_boumanになっている)は、ランダムに生成された名前です。なので、違う名前で表示されていると思います。コンテナとは?
コンテナを起動したわけですが、コンテナとは一体なんなのでしょうか?簡単にいうと、コンテナはホストマシンにある他の全てのプロセスから隔離された、シンプルな別のプロセスです。隔離には、カーネルの名前空間とCgroups、長い間Linuxで使用されていた機能を使用します。Dockerはこれらの機能を親しみやすく使いやすいものにするために取り組んできました。
コンテナイメージとは?
コンテナを実行する際には、隔離されたファイルシステムを使用します。このファイルシステムはコンテナイメージから供給されます。イメージにはコンテナのファイルシステムが含まれるので、アプリケーションを実行するのに必要な全ての依存関係・設定・スクリプト・バイナリなどはすべてイメージに含まれている必要があります。また、イメージには、環境変数や起動時のデフォルトコマンド、その他のメタデータなどコンテナの設定も含まれています。
イメージのレイヤ、ベストプラクティスなどの詳細な内容については後で紹介します。
情報
chrootをご存知なら、コンテナはchrootの拡張版だと考えてください。ファイルシステムは、ただイメージから持ってきたものです。しかし、コンテナにはchrootより強力な分離機能が追加されています。使うアプリの紹介
ここからは、Node.jsで動くシンプルなリスト管理アプリを使って進めていきます。Node.jsが分からなくても問題ありません。JavaScriptについての知識も必要ないです。
ここでは、開発チームはとても小さく、MVP(実用最小限の製品)を示すためにシンプルなアプリを作っている状況を想定します。大規模なチームや複数の開発者などに対してどう動作するかを考える必要はなく、どうやって動くか、何ができるのかを示すためのアプリを作成します。
アプリを手に入れる
アプリケーションを動かす前に、アプリケーションのソースコードをPCに入れる必要があります。実際のプロジェクトでは、リポジトリからクローンするのが一般的だと思います。しかし、このチュートリアルでは、アプリケーションが入ったZIPファイルを作成しておいたので、そちらを使用します。
ZIPファイルをダウンロードしたら、ZIPファイルを開いて、解凍してください。
解凍したら、任意のエディターでプロジェクトを開いてください。エディターをインストールしていない場合、Visual Studio Codeを使用してください。
package.jsonと2つのサブディレクトリ(srcとspec)が表示されるはずです。アプリのコンテナイメージを作成する
アプリケーションを構築するには、
Dockerfileを使用します。Dockerfileは、コンテナイメージを作成するために使われるテキストベースの命令スクリプトです。今までにDockerfileを作成したことのある方なら、下のDockerfileには欠陥があると分かるかもしれません。それについては後で説明します。1.以下の内容を書き込んだ
Dockerfileをpackage.jsonがあるディレクトリに作成してください。FROM node:12-alpine WORKDIR /app COPY . . RUN yarn install --production CMD ["node", "src/index.js"]
Dockerfileに.txtなどの拡張子が付いていないか確認してください。エディターによっては自動的に拡張子をつけてしまい、次のステップでエラーになる場合があります。2.移動していない場合は、ターミナルを開いて
Dockerfileのあるappディレクトリ に移動します。docker buildコマンドを使用してコンテナイメージを構築しましょう。docker build -t getting-started .このコマンドは、Dockerfileを使用して、新しいコンテナイメージを構築します。たくさんの"レイヤ"がインストールされたのに気がついたと思います。なぜかというと、
node:12-alpineイメージを起点にすることをビルダーに指示したからです。しかし、そのイメージがPC上になかったので、イメージをダウンロードする必要がありました。イメージがダウンロードされた後、アプリケーションをコピーし、
yarnを使用してアプリケーションの依存関係をインストールしました。CMD命令は、このイメージからコンテナが起動されたときに実行されるデフォルトのコマンドを指定します。最後に、
-tフラグは、イメージにタグを付けます。これは、イメージに人間の理解しやすい名前をつけるものだと考えてください。ここでは、イメージにgetting-startedと名付けたので、コンテナを起動するときはこの名前を参照することができます。
docker buildコマンドの最後につけた.は、DockerがカレントディレクトリにあるDockerfileを探すことを示しています。アプリのコンテナを起動する
イメージは用意したので、アプリケーションを実行させてみましょう。それには、
docker runコマンドを使用します。(すでに一度使ったのを覚えていますか?)1.
docker runコマンドを使用してコンテナを起動し、先ほど作成したイメージの名前を指定してください。docker run -dp 3000:3000 getting-started
-dフラグと-pフラグを覚えていますか?新しいコンテナをデタッチ(バックグランド実行)モードで起動し、ホストの3000番ポートをコンテナの3000番ポートにマッピングしました。ポートマッピングをしなかった場合、アプリケーションにアクセスすることはできません。2.数秒後、http://localhost:3000をWebブラウザで開いてみてください。アプリが表示されるはずです。
3.1つか2つアイテムを追加してみて、期待通りの動作になるか確認してください。アイテムに完了のチェックを入れたり、アイテムを削除することができます。フロントエンドがアイテムをバックエンドに保存できています。とても簡単でしょう?
この時点で、いくつかのアイテムを持つTodoリスト管理アプリができました。それでは、少し変更を加えながら、コンテナの管理について学んでいきましょう。
Dockerダッシュボードを見てみると、コンテナが2つ起動しているのが分かります。(このチュートリアル自身と、起動したばかりのアプリコンテナです。)
要約
この章では、コンテナイメージの構築についての基本的なことを学び、そのためのDockerfileを作成しました。イメージを構築してから、コンテナを起動し、実行中のアプリを触ってみました。
次は、アプリに修正を加えて、実行中のアプリを新しいイメージで更新する方法を学びましょう。途中で、便利なコマンドもいくつか学びます。
アプリをアップデートする
ちょっとした機能のリクエストとして、プロダクトチームからToDoリストのアイテムが存在しないときに表示される「空のテキスト」を変更してほしいという依頼がありました。以下のように変更したいとのことです。
You have no todo items yet! Add one above!
簡単ですよね?この変更を加えていきます。
ソースコードを更新する
1.
src/static/js/app.jsの56行目を書き換えて、新しいテキストが使用されるようにします。- <p className="text-center">No items yet! Add one above!</p> + <p className="text-center">You have no todo items yet! Add one above!</p>2.先ほど使ったものと同じコマンドを使って、更新したイメージをビルドしましょう。
docker build -t getting-started .3.更新したコードを使って新しいコンテナを起動しましょう。
docker run -dp 3000:3000 getting-startedあ゛!多分こんなエラーが表示されたと思います。(IDは違います)
docker: Error response from daemon: driver failed programming external connectivity on endpoint laughing_burnell (bb242b2ca4d67eba76e79474fb36bb5125708ebdabd7f45c8eaf16caaabde9dd): Bind for 0.0.0.0:3000 failed: port is already allocated.何が起こったんでしょうか。古いコンテナが動いていたので、新しいコンテナを立ち上げることができなかったのです。この問題が起こった理由として、特定のポートをリッスンできるのはコンテナがあるPC上で一つのプロセス(コンテナを含む)だけですが、コンテナは3000番ポートを既に使っていたからです。このエラーを解消するためには、古いコンテナを削除する必要があります。
古いコンテナを置き換える
コンテナを削除するためには、まず停止させる必要があります。停止してしまえば、削除できます。古いコンテナを削除するには、2通りの方法があります。お好きな方法をお選びください。
CLIでコンテナを削除する
1.
docker psコマンドを使用してコンテナのIDを取得します。docker ps2.
docker stopコマンドを使用してコンテナを停止させます。# <the-container-id>はdocker psコマンドで取得したIDで置き換えてください。 docker stop <the-container-id>3.コンテナを停止したら、
docker rmコマンドで削除します。docker rm <the-container-id>応用
docker rmコマンドに"force"フラグを追加することで、一つのコマンドでコンテナの停止と削除を行うことができます。例)
docker rm -f <the-container-id>Dockerダッシュボードを使ってコンテナを削除する
Dockerダッシュボードを開いて、二回クリックするだけでコンテナを削除することができます。コンテナIDを探して削除するよりはるかに簡単です。
ダッシュボードを開いて、アプリのコンテナにカーソルを合わせると、アクション一覧が右側に表示されます。
ゴミ箱ボタンをクリックすると、コンテナが削除されます。
削除を確認したら完了です。
更新したアプリを起動する
1.更新したアプリを起動します。
docker run -dp 3000:3000 getting-started2.http://localhost:3000でブラウザを再読み込みすると、アップデートされたテキストが表示されます。
要約
アプリの更新ができた一方で、特筆すべきことが2点ありました。
存在していた全てのToDoリストが全て消滅してしまったことです。これではいいアプリとは言えません。これについては近いうちに話そうと思います。
小さな変更だったのにもかかわらず、多くのステップを踏む必要がありました。次の章では、変更を加えるたびに新しいコンテナの再構築と起動を行わなくてもコードを更新する方法について紹介します。
永続性についてお話しする前に、イメージを他人と共有する方法について学びましょう。
アプリを共有する
イメージを作成できたので、早速共有してみましょう。Dockerイメージを共有するには、Dockerレジストリを使用する必要があります。デフォルトのレジストリはDocker Hubであり、今まで使ってきたイメージもそこから持ってきたものでした。
レポジトリの作成
イメージをプッシュするには、まずDocker Hub上にレポジトリを作成する必要があります。
Docker Hubにアクセスして、必要ならログインしてください。
Create Repositoryボタンをクリックしてください。
レポジトリ名は
getting-startedを指定してください。公開レベルがPublicになっていることを確認してください。Createボタンをクリックしてください。
ページの右側を見ると、Dockerコマンドというのがあると思います。ここには、このレポジトリにプッシュするために実行する必要のあるコマンドの例が記載されています。
イメージをプッシュする
1.Docker Hubにあったpushコマンドをコマンドライン上で実行してみてください。注意点として、コマンドのネームスペースは"docker"ではなく、自分の名前空間を使用してください。
$ docker push docker/getting-started The push refers to repository [docker.io/docker/getting-started] An image does not exist locally with the tag: docker/getting-startedなぜ失敗してしまったのでしょうか。このpushコマンドはdocker/getting-startedという名前のイメージを探しましたが、見つからなかったのです。
docker image lsを実行してみてもイメージは見つかりません。
この問題を解決するためには、これまでに作成したイメージに別の名前をつけるため、タグ付けする必要があります。和訳者メモ
docker image lsと同じ動作をするコマンドにdocker imagesがあります。これはDockerコマンドの再編成によるもので、docker image lsの方が新しく、推奨されています。このチュートリアルでは、今後も同じ動作をするコマンドを他に持つコマンドが登場します。2.
docker login -u YOUR-USER-NAMEコマンドを使用して、Docker Hubにログインします。3.
docker tagコマンドを使用して、getting-startedイメージに新しい名前をつけます。YOUR-USER-NAMEはあなたのDocker IDに置き換えてください。docker tag getting-started YOUR-USER-NAME/getting-started4.もう一度pushコマンドを実行してみましょう。イメージ名にタグは追加していないので、Docker Hubからコピー&ペーストしてきた場合は、
tagnameの部分は削除してください。タグを指定しない場合、Dockerはlatestというタグを使用します。docker push YOUR-USER-NAME/getting-startedイメージを新しいインスタンス上で動かす
イメージを構築してレジストリにプッシュできたので、新しいインスタンス上でこのコンテナイメージを動かしてみましょう。そのために、Play with Dockerを使用します。
1.ブラウザでPlay with Dockerを開きます。
2.Docker Hubアカウントでログインします。
3.ログインしたら、左のバーにある「+ ADD NEW INSTANCE」リンクをクリックします(見当たらない場合、ブラウザを少し横に広げてください)。数秒後、ブラウザ上にターミナルウィンドウが表示されます。
4.ターミナル上で、プッシュしたアプリを起動させましょう。
docker run -dp 3000:3000 YOUR-USER-NAME/getting-startedイメージが取得されたのち、起動します。
5.3000と書かれたバッジが表示されるので、それをクリックすると、変更を加えたアプリが表示されます。やりましたね。3000と書かれたバッジが表示されない場合、「Open Port」ボタンをクリックして、3000と入力してください。
要約
この章では、イメージをレジストリにプッシュして共有する方法を学びました。それから、新しいインスタンスに入って、プッシュしたイメージを起動しました。これは、CIパイプラインでは一般的なことで、パイプラインがイメージを作成してレジストリにプッシュすると、本番環境では最新版のイメージを使用できるようになります。
ここまでは理解できたので、さっきの章の最後の話題に戻りましょう。アプリを再起動すると、ToDoリストのアイテムが全て消去されてしまうという問題がありました。当然それでは良いUX(ユーザー体験)とは言えないので、どうやってリスタートした後もデータを保持するかを学びましょう。
データベースを永続化する
気がついたと思いますが、ToDoリストはコンテナを起動するたびに初期化されています。なぜでしょうか。コンテナがどのように動作しているのかをもう少し掘り下げてみましょう。
コンテナのファイルシステム
コンテナが起動したとき、イメージの様々なレイヤがファイルシステムのために使用されます。また、それぞれのコンテナは作成/更新/削除を行うための「スクラッチスペース」を確保します。同じイメージが使われている場合でも、変更は別のコンテナに影響しません。
実際に見てみる
実際にみてみるために、2つのコンテナを起動させ、それぞれにファイルを作成しましょう。片方のコンテナでファイルを作成しても、もう一方のコンテナでそのファイルが有効で無いことがわかります。
1.1から10000までのランダムな数字を書き込んだ
/data.txtを作成するubuntuコンテナを起動します。docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"コマンドに詳しい方なら、Bashシェルを起動して、二つのコマンドを呼び出していることが分かると思います(そのために
&&を使用しています)。最初の部分で、ランダムな1つの数字を/data.txtに書き込んでいます。2つ目のコマンドは、コンテナの実行を維持するためにファイルを監視し続けているだけです。2.出力されたものを確認するために、
execでコンテナの中に入ってみましょう。ダッシュボードを開いて、起動しているubuntuイメージの最初のアクションをクリックすることで、これを行うことができます。ubuntuコンテナの中でシェルが起動しているのがわかったと思います。以下のコマンドを実行して、
/data.txtの内容を表示してみましょう。それができたら、またターミナルを閉じてください。cat /data.txt同じことをコマンドラインを使って行いたい場合は、
docker execを使用してください。docker psでコンテナのIDを取得したのち、以下のコマンドでファイルの内容を取得できます。docker exec <container-id> cat /data.txtランダムな数字が表示されるはずです。
3.別の
ubuntuコンテナを立ち上げて、同じファイルが存在しないか確認してみましょう。
docker run -it ubuntu ls /
data.txtがありません。書き込まれた先は、最初のコンテナのためのスクラッチスペースだったからです。4.
docker rm -fコマンドを使用して最初のコンテナを削除します。コンテナのボリューム
ここまでで、コンテナは起動時にイメージの定義から始めることがわかりました。コンテナはファイルの作成、更新、削除を行うことができますが、コンテナが削除されると失われ、全ての変更はそのコンテナに限定されます。しかし、ボリュームを使えば、その全てを変更することができます。
ボリュームは、コンテナの特定のファイルシステムパスがホストマシンに接続できるようにする機能を提供します。コンテナ内のディレクトリがマウントされている場合、そのディレクトリの変更はホストマシンにも影響します。コンテナを再起動しても同じディレクトリをマウントした場合、同じファイルを参照できるというわけです。
ボリュームには2通りのタイプがあります。どちらも使うのですが、とりあえずネームドボリュームを使ってみましょう。
Todoデータを永続化する
ToDoアプリはデータを
/etc/todos/todo.dbにあるSQLite Databaseに保存しています。SQLiteが分からなくても気にしないでください。SQLiteはシンプルなリレーショナルデータベースで、一つのファイルに全てのデータを保存しています。これは大きなデータを扱う上ではベストな方法ではないのですが、小さなデモアプリでは有効です。違うデータベースエンジンに切り替える方法については後述します。データベースが単一のファイルであるため、このファイルをホストで永続化して次のコンテナから参照できるようにすれば、中断した最後のところから再開できるようになるはずです。ボリュームを作成してデータが保存されているディレクトリに接続(マウントともいいます)すれば、データが永続化できます。コンテナが
todo.dbファイルに書き込むと、ボリューム内のホストに保持されます。軽く触れておくと、これから使おうとしているのは、ネームドボリュームです。ネームドボリュームはデータを入れるバケツと考えてください。Dockerはディスク上に物理的な領域を確保するので、ボリュームの名前だけ覚えておけば良いです。ボリュームを利用するときに、Dockerが正しいデータが取得されているかを検証してくれます。
1.
docker volume createコマンドでボリュームを作成します。docker volume create todo-db2.すでに立ち上げたToDoアプリは持続したボリュームを使用せずに実行されているので、ダッシュボードを使うか
doker rm -f <id>コマンドを使用して停止させてください。3.ToDoアプリのコンテナを起動するのですが、ボリューム接続を指定するのに
-vフラグを付け加えてください。ネームドボリュームを使用して/etc/todosに接続し、全てのファイルをキャプチャします。docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started4.コンテナを立ち上げたら、アプリを開いて、ToDoリストにいくつかアイテムを追加してみてください。
5.ToDoアプリのコンテナを削除します。ダッシュボードを使用するか、
docker psでIDを取得してからdocker rm -f <id>で削除を行ってください。6.上記と同じコマンドを使用して、新しいコンテナを起動してください。
7.リストが表示されることを確認したら、コンテナを削除して次に進みましょう。
データの永続化の方法を理解できましたね。
応用
ネームドボリュームとバインドマウント(これについては後で話します)は、Dockerをインストールした時からサポートされている2通りのボリュームですが、ドライバプラグインも数多く存在し、NFS、SFTP、NetAppなどをサポートしています。これは、SwarmやKubernetesなどのクラスタ環境内の複数のホスト上でコンテナを起動させたときにとても重要になります。
ボリュームについて深く知る
多くの人に「ネームドボリュームを使ったときにDockerがデータを保存する実際の場所はどこなんですか?」とよく聞かれます。知りたいのであれば、
docker volume inspectコマンドを使用すれば可能です。docker volume inspect todo-db [ { "CreatedAt": "2019-09-26T02:18:36Z", "Driver": "local", "Labels": {}, "Mountpoint": "/var/lib/docker/volumes/todo-db/_data", "Name": "todo-db", "Options": {}, "Scope": "local" } ]
Mountpointというのが、ディスク上にデータが保存されている実際の場所です。ほとんどのマシンでは、ホストからこのディレクトリにアクセスするのにRoot権限が必要になることに注意してください。Dockerデスクトップでボリュームデータに直接アクセスする
Dockerデスクトップで実行している間、Dockerコマンドは実際にはマシン上の小さな仮想マシン内で実行されています。Mountpointディレクトリの実際の内容を見たければ、まず仮想マシン内に入る必要があります。
要約
この時点で、存続したまま再起動できる機能的なアプリケーションを作ることができました。投資家に見せびらかして、私たちのビジョンを理解してもらえることを願っています。
しかしながら、変更を加えるたびにイメージを再構築するのは少し時間がかかり過ぎです。変更を加えるのにはもっと良い方法があるんです。バインドマウント(さっきほのめかしたやつ)が、その方法です。さっそく見てみましょう。
バインドマウントを使用する
前の章で、ネームドボリュームを使用して、データベースの永続化を行いました。ネームドボリュームは、データの保存場所について気にする必要がないので、単にデータを保存したい場合には有効です。
バインドマウントを使えば、ホスト上の正確なMountpointをコントロールできます。データの永続化にも使用できますが、追加のデータをコンテナに提供するのによく使われます。アプリを開発する場合、バインドマウントでソースコードをコンテナに接続して、コードを変更したり、応答したり、変更をすぐに確認したりできます。
Nodeで作られたアプリの場合、ファイルの変更を監視してアプリケーションを再起動するのにはnodemonが最適です。同じようなツールは、ほとんどの言語とフレームワークに存在します。
ボリュームのタイプ比較表
バインドマウントとネームドボリュームは、Dockerエンジンが備えた2つの主なボリュームタイプです。一方で、追加のボリュームドライバは、他のユースケース(SFTP, Ceph, NetApp, S3 など)で役立ちます。
Named Volumes バインドマウント ホストの場所 Dockerが選ぶ 自分が選ぶ マウントの例 ( -vを使用)my-volume:/usr/local/data /path/to/data:/usr/local/data コンテナのコンテンツで新しいボリュームを作成する Yes No ボリュームドライバのサポート Yes No デベロッパモードのコンテナを起動する
開発段階で使えるコンテナを起動してみましょう。以下のことを行います。
- ソースコードをコンテナにマウントする。
- "dev" dependenciesを含む全ての依存関係をインストールする。
- nodemonを起動してファイルの変更を監視する
では、始めましょう。
1.今までに使用した
getting-startedコンテナが起動していないことを確認してください。2.以下のコマンドを実行してください。何をしているかの説明もしていきます。
docker run -dp 3000:3000 \ -w /app -v "$(pwd):/app" \ node:12-alpine \ sh -c "yarn install && yarn run dev"PowerShellを使っている場合は、以下のコマンドを使用してください。
docker run -dp 3000:3000 ` -w /app -v "$(pwd):/app" ` node:12-alpine ` sh -c "yarn install && yarn run dev"
-dp 3000:3000: 今までと同じです。デタッチ(バックグラウンド)モードで起動し、ポートマッピングを作成します。
-w /app: "ワーキングディレクトリ"か、コマンドが実行されるカレントディレクトリを指定します。
-v "$(pwd):/app": コンテナのホストから、/appディレクトリにカレントディレクトリをバインドマウントします。
node:12-alpine: 使用するイメージです。このイメージはDockerfileで指定した通り、アプリのベースイメージとなっていることに気をつけてください。
sh -c "yarn install && yarn run dev": コマンドです。sh(alpineにはbashがありません)を使用してシェルを起動し、yarn installで全ての依存関係をインストールしてから、yarn run devを実行しています。package.jsonを見てみると、devスクリプトはnodemonを起動することがわかります。3.
docker logs -f <container-id>コマンドでログを見ることができます。これを見れば、準備ができていることが分かります。docker logs -f <container-id> $ nodemon src/index.js [nodemon] 1.19.2 [nodemon] to restart at any time, enter `rs` [nodemon] watching dir(s): *.* [nodemon] starting `node src/index.js` Using sqlite database at /etc/todos/todo.db Listening on port 3000ログを見終わったら、
Ctrl+Cで終了することができます。4.では、アプリに変更を加えてみましょう。
src/static/js/app.jsファイル内の「Add Item」ボタンを「Add」に変更してみましょう。109行目にあります。- {submitting ? 'Adding...' : 'Add Item'} + {submitting ? 'Adding...' : 'Add'}5.ページをリフレッシュ(もしくは開く)だけで、ブラウザにほとんど即座に変更が反映されているのがわかるはずです。Nodeサーバーを再起動するのには数秒かかるので、エラーになった場合は、数秒後にリフレッシュしてみてください。
6.他にも変更を加えてみてください。それが終わったら、コンテナを停止してから、
docker build -t getting-started .を使用して新しいイメージをビルドしてください。バインドマウントを使用することは、ローカル開発においてとても一般的なことです。その利点として、開発マシンにビルドツールや環境がインストールされている必要がないことがあります。
docker runコマンドだけで、開発環境はプルされ、準備が完了します。のちの章でDocker Composeについて話す予定ですが、これはたくさんのフラグがついたコマンドをシンプルにすることができます。要約
データベースを永続化し、投資家と創設者の要求と要望に迅速に対応できるようになりました。でも、ちょっと待ってください。素晴らしいニュースが飛び込んできました!
あなたのプロジェクトは、将来的に開発されることになりました。
製品化に備えて、データベースをSQLiteより拡張性の高いものに移行する必要があります。単純に考えて、リレーショナルデータベースはそのままに、MySQLを使用するべきでしょう。しかし、どうやってMySQLを動かせば良いのでしょうか?どうやってコンテナ間での通信を許可すれば良いのでしょうか?それについて、次の章で話していこうと思います。
複数のコンテナを持つアプリ
ここまで、一つのコンテナのアプリで作業してきました。しかし、アプリケーションにMySQLを追加したいです。「MySQLはどこで動かせば良いんですか?同じコンテナに動かして別々に起動すれば良いですか?」という質問がよくあります。一般的に、各コンテナは一つのことのみを行うべきです。それには、いくつか理由があります。
APIやフロントエンドをデータベースと異なる方法で拡張させる可能性が高い。
コンテナを分離することで、バージョンの更新を分離して行うことができます。
ローカルのデータベース用コンテナを使用することもできますが、本番環境ではデータベースを管理するサービスを使用したいと思うかもしれません。その場合、アプリと一緒にデータベースエンジンを製品に含める必要はありません。
複数のプロセスを実行するには、プロセスマネージャ(コンテナは1つのプロセスしか起動しません)が必要になり、コンテナの起動/停止が複雑になります。
もっと他にも理由はあります。なので、こんなふうに動作するようにアプリをアップデートしていきます。
コンテナのネットワーク
思い出して欲しいのですが、コンテナはデフォルトで独立して動作し、同じマシン上の他のプロセスやコンテナについて何も知りません。では、どうやってコンテナが他のコンテナと通信できるようにすれば良いのでしょうか?その答えがネットワークです。あなたがネットワークエンジニアである必要はありません。このルールだけ覚えておいてください。
2つのコンテナが同じネットワーク内にあるとき、お互いに通信することができます。同じネットワーク内にないときは、通信できません。
MySQLを起動する
コンテナをネットワーク上に配置するには2通りの方法があります。1つ目は、スタート時に割り当てる方法。2つ目は、すでにあるコンテナを接続する方法です。今回は、最初にネットワークを作成してから、起動したMySQLコンテナを接続しましょう。
1.ネットワークを作成します。
docker network create todo-app2.MySQLコンテナを起動して、ネットワークに接続します。また、データベースの初期化に使用する環境変数をいくつか定義します。(MySQL Docker Hub listingの"Environment Variables"という章を参照してください)
docker run -d \ --network todo-app --network-alias mysql \ -v todo-mysql-data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=secret \ -e MYSQL_DATABASE=todos \ mysql:5.7PowerShellを使用している場合は、以下のコマンドを使用してください。
docker run -d ` --network todo-app --network-alias mysql ` -v todo-mysql-data:/var/lib/mysql ` -e MYSQL_ROOT_PASSWORD=secret ` -e MYSQL_DATABASE=todos ` mysql:5.7
--network-aliasフラグを指定しました。これについては後述します。プロのための情報
todo-mysql-dataという名前のボリュームを使用し、それをMySQLのデータが保存される/var/lib/mysqlにマウントしました。 しかし、docker volume createコマンドは使用していません。Dockerはネームドボリュームを使おうとしていることを認識して、自動でボリュームを作成してくれたのです。3.データベースが起動しているか確認するために、データベースに接続して接続されているか確認します。
docker exec -it <mysql-container-id> mysql -pもしパスワードを聞かれたら、secretと入力してください。MySQLシェル内で、データベース一覧を表示し、
todosデータベースがあることを確認してください。mysql> SHOW DATABASES;このように表示されるはずです。
+--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | | todos | +--------------------+ 5 rows in set (0.00 sec)
todosデータベースを用意することができました。MySQLに接続する
MySQLが起動したことは確認できたので、実際に使ってみましょう。でも、同じネットワークで別のコンテナを起動したとして、どうやってコンテナを探せば良いのでしょうか。(各コンテナはそれぞれ別のIPアドレスを持っていることを覚えておいてください。)
これを理解するために、ネットワークに関する問題のトラブルシューティングやデバッグに便利なツールが入ったnicolaka/netshootコンテナを利用しましょう。
1.nicolaka/netshootイメージを使用して、新しいコンテナを立ち上げましょう。同じネットワークに接続されていることを確認してください。
docker run -it --network todo-app nicolaka/netshoot2.コンテナ内で便利なDNSツールである
digコマンドを使用します。ホスト名がmysqlのIPアドレスを探しましょう。dig mysqlすると、以下のように表示されるはずです。
; <<>> DiG 9.14.1 <<>> mysql ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32162 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;mysql. IN A ;; ANSWER SECTION: mysql. 600 IN A 172.23.0.2 ;; Query time: 0 msec ;; SERVER: 127.0.0.11#53(127.0.0.11) ;; WHEN: Tue Oct 01 23:47:24 UTC 2019 ;; MSG SIZE rcvd: 44「ANSWER SECTION」というところを見ると、
mysqlのAレコードが172.23.0.2とわかります(あなたのIPアドレスは違う値になっている可能性が高いです)。mysqlは通常有効なホスト名ではありませんが、Dockerはmysqlというネットワークエイリアスを持つコンテナのIPアドレスを解決することができました(--network-aliasフラグを使用したのを覚えていますか?)。これが意味するのは、ToDoアプリについても
mysqlという名前のホストに接続するだけで、データベースと通信できるということです。これほど簡単なことはありません。MySQLを使ったアプリを動かす
ToDoアプリでは、MySQLコネクション設定を指定するいくつかの環境変数を設定することができます。詳細は以下の通りです。
MYSQL_HOST: 稼働中のMySQLサーバのホスト名です。
MYSQL_USER: 接続に使用するユーザ名です。
MYSQL_PASSWORD: 接続に使用するパスワードです。
MYSQL_DB: 接続後に使用するデータベースです。注意
開発環境でコネクションの設定に環境変数を使うことは問題ありませんが、本番環境で動いているアプリケーションで使用することはとても推奨されない行為です。Dockerで以前セキュリティを担当していたDiogo Monicaさんがその理由を説明する素晴らしい記事を書いてくれています。
もっと安全な手法としては、コンテナのオーケストレーションフレームワークによって提供されるシークレットサポートを利用することがあります。ほとんどの場合、これらのシークレットファイルは稼働しているコンテナにマウントされます。多くのアプリ(MySQLイメージとToDoアプリを含めて)はファイルを含むファイルを指す
_FILE接尾辞がついた環境変数もサポートしています。例として、変数
MYSQL_PASSWORD_FILEを設定すると、アプリは参照されたファイルの内容をコネクションパスワードとして使用します。なお、Dockerはこれらの環境変数を一切サポートしません。アプリは、環境変数を探して、ファイルの内容を取得する方法を知っておく必要があります。説明は終わったので、コンテナを起動させましょう。
1.前述した環境変数をそれぞれ指定して、コンテナがアプリのネットワークに接続できるようにしましょう。
docker run -dp 3000:3000 \ -w /app -v "$(pwd):/app" \ --network todo-app \ -e MYSQL_HOST=mysql \ -e MYSQL_USER=root \ -e MYSQL_PASSWORD=secret \ -e MYSQL_DB=todos \ node:12-alpine \ sh -c "yarn install && yarn run dev"PowerShellを使用している場合は、以下のコマンドを使用してください。
docker run -dp 3000:3000 ` -w /app -v "$(pwd):/app" ` --network todo-app ` -e MYSQL_HOST=mysql ` -e MYSQL_USER=root ` -e MYSQL_PASSWORD=secret ` -e MYSQL_DB=todos ` node:12-alpine ` sh -c "yarn install && yarn run dev"2.コンテナのログを見ると(
docker logs <container-id>)、MySQLデータベースを使用していることを示すメッセージがあります。# Previous log messages omitted $ nodemon src/index.js [nodemon] 1.19.2 [nodemon] to restart at any time, enter `rs` [nodemon] watching dir(s): *.* [nodemon] starting `node src/index.js` Connected to mysql db at host mysql Listening on port 30003.ブラウザでアプリを開いて、ToDoリストにいくつかアプリを追加してみてください。
4.MySQLデータベースに接続して、アイテムが書き込まれているか確認しましょう。パスワードはsecretです。
docker exec -ti <mysql-container-id> mysql -p todosそしてMySQLシェル内で、以下を実行してください。
mysql> select * from todo_items; +--------------------------------------+--------------------+-----------+ | id | name | completed | +--------------------------------------+--------------------+-----------+ | c906ff08-60e6-44e6-8f49-ed56a0853e85 | Do amazing things! | 0 | | 2912a79e-8486-4bc3-a4c5-460793a575ab | Be awesome! | 0 | +--------------------------------------+--------------------+-----------+もちろん、あなたのアイテムが含まれているので、テーブルの内容は異なります。でも、ここにアイテムが保存されているのが分かりましたね。
Dockerダッシュボードを見ると、2つのコンテナが起動しています。しかし、一つのアプリにグループ化されているという表示はありません。これを改善する方法について見ていきましょう。
要約
独立したコンテナで動く外部のデータベースにデータを保存するアプリケーションを作ることができました。コンテナのネットワークについて少し学び、DNSを使用してサービスの発見を行う方法を理解しました。
しかし、このアプリを立ち上げるために必要な全てのことに、圧倒されているかもしれません。ネットワークを作成し、コンテナを起動し、全ての環境変数を指定し、ポートを解放したりする必要があります。覚えることが多すぎて誰かに伝えるのが難しくなっているのは確かです。
次の章では、Docker Composeについてお話しします。Docker Composeを使えば、アプリケーションスタックをより簡単に共有して、一つのシンプルなコマンドだけで起動するようにできます。
Docker Composeを使用する
Docker Composeは、マルチコンテナアプリの定義と共有をしやすくするために開発されたものです。Composeを使えば、YAMLファイルを作成することでサービスを定義して、1つのコマンドで起動したり、停止したりすることができます。
Composeを使用する大きな利点は、ファイルにアプリケーションスタックを定義し、(バージョン管理されている)プロジェクトリポジトリのルートに保存して、誰でも簡単にプロジェクトにコントリビュートできるようにできることです。実際、GitHubやGitLabにはそのようなプロジェクトがたくさんあります。
では、早速初めていきましょう。
Docker Composeをインストールする
Dockerデスクトップ/ツールボックスをWindowsかMacにインストールしているなら、すでにDocker Composeはインストールされています。また、Play-with-Dockerのインスタンスにも、Docker Composeはインストールされています。Linuxマシンをお使いなら、こちらのページに従ってDocker Composeをインストールしていただく必要があります。
インストールが完了したら、下記のコマンドを実行して、バージョン情報を確認できるはずです。
docker-compose versionComposeファイルを作成する
1.アプリのプロジェクトのルートに、
docker-compose.ymlというファイルを作成します。2.Composeファイルでは、スキーマバージョンを定義するところから始めます。ほとんどの場合、最新版を使うのが良いです。最新のスキーマバージョンと互換性については、Composeファイルのレファレンスを参照してください。
version: 3.73.次に、アプリの一部として動かしたいサービス(もしくはコンテナ)のリストを定義します。
version: "3.7" services:次は、サービスをComposeファイルに移行していきましょう。
アプリのサービスを定義する
思い出していただきたいのですが、以下はアプリのコンテナを定義するのに使用したコマンドです。
docker run -dp 3000:3000 \ -w /app -v "$(pwd):/app" \ --network todo-app \ -e MYSQL_HOST=mysql \ -e MYSQL_USER=root \ -e MYSQL_PASSWORD=secret \ -e MYSQL_DB=todos \ node:12-alpine \ sh -c "yarn install && yarn run dev"PowerShellを使用している場合は、以下のようなコマンドを使用しました。
docker run -dp 3000:3000 ` -w /app -v "$(pwd):/app" ` --network todo-app ` -e MYSQL_HOST=mysql ` -e MYSQL_USER=root ` -e MYSQL_PASSWORD=secret ` -e MYSQL_DB=todos ` node:12-alpine ` sh -c "yarn install && yarn run dev"1.最初に、コンテナのためのサービスエントリとイメージを定義します。サービス名は任意のものを選ぶことができます。名前は自動的にネットワークエイリアスとして使用されるので、MySQLサービスを定義するのが楽になります。
version: "3.7" services: app: image: node:12-alpine2.一般的に、commandは
imageの定義の近くに書きますが、順序は自由です。では、ファイルに書き込みましょう。version: "3.7" services: app: image: node:12-alpine command: sh -c "yarn install && yarn run dev"3.コマンドの
-p 3000:3000という部分をportsに移行しましょう。ここでは簡略した書き方を使用しますが、冗長で長い書き方も同じように使用できます。version: "3.7" services: app: image: node:12-alpine command: sh -c "yarn install && yarn run dev" ports: - 3000:30004.次に、ワーキングディレクトリ(
-w /app)とボリュームマッピング(-v "$(pwd):/app")をworking_dirとvolumesに移行します。Volumesの書き方にも短いのと長いのがあります。Docker Composeのボリューム定義の長所として、カレントディレクトリからの相対パスが使用できることがあります。
version: "3.7" services: app: image: node:12-alpine command: sh -c "yarn install && yarn run dev" ports: - 3000:3000 working_dir: /app volumes: - ./:/app5.最後に、環境変数を
environmentキーを使って移行します。version: "3.7" services: app: image: node:12-alpine command: sh -c "yarn install && yarn run dev" ports: - 3000:3000 working_dir: /app volumes: - ./:/app environment: MYSQL_HOST: mysql MYSQL_USER: root MYSQL_PASSWORD: secret MYSQL_DB: todosMySQLサービスを定義する
では、MySQLサービスを定義します。コンテナのために使用したコマンドは以下の通りです。
docker run -d \ --network todo-app --network-alias mysql \ -v todo-mysql-data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=secret \ -e MYSQL_DATABASE=todos \ mysql:5.7PowerShellを使用している場合は、以下のコマンドを使用してください。
docker run -d ` --network todo-app --network-alias mysql ` -v todo-mysql-data:/var/lib/mysql ` -e MYSQL_ROOT_PASSWORD=secret ` -e MYSQL_DATABASE=todos ` mysql:5.71.まず新しいサービスを定義して、
mysqlと名付けると、自動でネットワークエイリアスを取得します。使用するイメージを指定しましょう。version: "3.7" services: app: # The app service definition mysql: image: mysql:5.72.次に、ボリュームマッピングを指定します。
docker runでコンテナを起動したとき、ネームドボリュームが自動で作成されました。しかし、Composeを使用して起動したときには作成されません。トップレベルにあるvolume:でボリュームを定義してから、サービスコンフィグのマウントポイントを指定します。ボリューム名だけを指定すると、デフォルトのオプションが使用されます。ですが、他にも多くのオプションが利用可能です。version: "3.7" services: app: # The app service definition mysql: image: mysql:5.7 volumes: - todo-mysql-data:/var/lib/mysql volumes: todo-mysql-data:3.最後に、環境変数を指定します。
version: "3.7" services: app: # The app service definition mysql: image: mysql:5.7 volumes: - todo-mysql-data:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: secret MYSQL_DATABASE: todos volumes: todo-mysql-data:完成した
docker-compose.ymlは以下のようになります。version: "3.7" services: app: image: node:12-alpine command: sh -c "yarn install && yarn run dev" ports: - 3000:3000 working_dir: /app volumes: - ./:/app environment: MYSQL_HOST: mysql MYSQL_USER: root MYSQL_PASSWORD: secret MYSQL_DB: todos mysql: image: mysql:5.7 volumes: - todo-mysql-data:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: secret MYSQL_DATABASE: todos volumes: todo-mysql-data:アプリケーションスタックを起動する
docker-compose.ymlが用意できたので、あとは起動するだけです。1.まず、他にapp/dbのコピーが実行されていないことを確認してください。(
docker psとdocker rm -f <ids>を使用してください。)2.
docker-compose upコマンドを使用して、アプリケーションスタックを起動してください。-dフラグを追加して、バックグラウンドで実行されるようにします。docker-compose up -d実行すると、以下のように出力されるはずです。
Creating network "app_default" with the default driver Creating volume "app_todo-mysql-data" with default driver Creating app_app_1 ... done Creating app_mysql_1 ... doneネットワークと同じくボリュームも作成することができていますね。デフォルトでは、Docker Composeはアプリケーションスタックのネットワークを自動で作成します(Composeファイルで定義しなかったのはこのためです)。
和訳者メモ -
docker-compose upで以下のエラーが表示された場合[ERROR] [FATAL] InnoDB: Table flags are 0 in the data dictionary but the flags in file ./ibdata1 are 0x4800!以下のコマンドでボリューム削除すると直りました。最初に間違えて
mysql:latestでdocker-compose.ymlを書いたのが原因だったかもしれないです。docker volume rm app_todo-mysql-data3.
docker-compose logs -fコマンドを使用してログを見て見ましょう。各サービスのログが一つに集約されているのが分かると思います。これは、タイミングに関する問題を確認したいときにとても便利です。-fフラグはログを追従するので、生成されたログをライブ出力します。実際の出力は以下のようになります。
mysql_1 | 2019-10-03T03:07:16.083639Z 0 [Note] mysqld: ready for connections. mysql_1 | Version: '5.7.27' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL) app_1 | Connected to mysql db at host mysql app_1 | Listening on port 3000最初のラインの(色付けされている)サービス名は、メッセージを識別するのに役立ちます。特定のサービスについてのログを見たいときは、ログコマンドの末尾にサービス名を追加してください(例:
docker-compose logs -f app)。応用 - アプリを起動する前にデータベースを待機させる
実際には、アプリケーションを起動しても、MySQLが起動して準備が整ってから接続を試みます。Dockerには、コンテナが完全に起動、実行、準備されてから別のコンテナを起動するための組み込みのサポートは用意されていません。Nodeベースのプロジェクトでは、wait-portを使用できます。同じようなプロジェクトは、他の言語・フレームワークにおいても存在します。
4.アプリを開くと、起動しているのを確認できるはずです。そして、停止も一つのコマンドで行えます。
Dockerダッシュボードでアプリケーションスタックを確認する
Dockerダッシュボードを見ると、appという名前のグループがあります。これはDocker Composeのプロジェクト名で、コンテナをグループ化するのに使用されています。デフォルトでは、プロジェクト名は
docker-compose.ymlが配置されているディレクトリの名前です。appのドロップダウンを開くと、composeファイルで定義した2つのコンテナが確認できます。そちらの名前は
<プロジェクト名>_<サービス名>_<複製番号>というふうになっています。そのおかげで、どのコンテナがどのアプリか、どのコンテナがmysqlデータベースかということが分かりやすくなっています。全て停止する
docker-compose downを実行するか、Dockerダッシュボードでapp全体をゴミ箱に入れるだけで、全て停止できます。コンテナは停止され、ネットワークは削除されます。ボリュームの削除
デフォルトでは、
docker-compose downを実行しても、composeファイル内のネームドボリュームは削除されません。ボリュームを削除したい場合は、--volumesフラグを付け加える必要があります。Dockerダッシュボードでは、アプリスタックを削除してもボリュームは削除されません。
停止したら、他のプロジェクトに切り替えて
docker-compose upを実行するだけで、そのプロジェクトを開発することができます。とても簡単ですよね。要約
この章では、Docker Composeについてと、それによって複数サービスのアプリケーションの定義と共有がどれだけ簡単になるのかということを学びました。使っていたコマンドを適切なCompose形式に移行してComposeファイルを作成しました。
チュートリアルも終盤に入りました。しかし、これまで使ってきたDockerfileには大きな問題があるので、イメージ構築に関するベストプラクティスをいくつか取り上げていきたいと思います。では、みてみましょう。
イメージ構築のベストプラクティス
イメージのレイヤ
イメージの構成要素を確認できることをご存知ですか?
docker image historyコマンドを使用すれば、イメージに含まれる各レイヤの作成に使用されたコマンドを確認することができます。1.
docker image historyコマンドを使用して、このチュートリアルで以前作成したgetting-startedイメージのレイヤを見てみましょう。docker image history getting-startedすると、以下のように表示されるはずです(おそらくIDは異なります。)。
IMAGE CREATED CREATED BY SIZE COMMENT a78a40cbf866 18 seconds ago /bin/sh -c #(nop) CMD ["node" "src/index.j… 0B f1d1808565d6 19 seconds ago /bin/sh -c yarn install --production 85.4MB a2c054d14948 36 seconds ago /bin/sh -c #(nop) COPY dir:5dc710ad87c789593… 198kB 9577ae713121 37 seconds ago /bin/sh -c #(nop) WORKDIR /app 0B b95baba1cfdb 13 days ago /bin/sh -c #(nop) CMD ["node"] 0B <missing> 13 days ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B <missing> 13 days ago /bin/sh -c #(nop) COPY file:238737301d473041… 116B <missing> 13 days ago /bin/sh -c apk add --no-cache --virtual .bui… 5.35MB <missing> 13 days ago /bin/sh -c #(nop) ENV YARN_VERSION=1.21.1 0B <missing> 13 days ago /bin/sh -c addgroup -g 1000 node && addu… 74.3MB <missing> 13 days ago /bin/sh -c #(nop) ENV NODE_VERSION=12.14.1 0B <missing> 13 days ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B <missing> 13 days ago /bin/sh -c #(nop) ADD file:e69d441d729412d24… 5.59MBそれぞれにレイヤがイメージのレイヤを表しています。この画面では、イメージのベースが下部、最新のレイヤーが上部に表示されています。これを使えば、素早くそれぞれのレイヤを確認し、サイズの大きいイメージを突き止めるのに役立ちます。
2.いくつかの行が省略されていることに気がついたでしょうか。
--no-truncフラグを付け加えると、フル出力を得ることができます。(省略されたフラグを使って省略されていない出力を得るって、面白いですよね。)docker image history --no-trunc getting-startedレイヤーのキャッシュ
実際にレイヤーを見たわけですが、これはコンテナイメージのビルド時間を減らすことに関してとても重要な話です。
レイヤーを変更したら、下流の全てのレイヤーを再生成する必要があります。
使用していたDockerfileをもう一度見てみましょう。
FROM node:12-alpine WORKDIR /app COPY . . RUN yarn install --production CMD ["node", "src/index.js"]image historyの出力の話に戻ると、Dockerfileのそれぞれのコマンドがイメージの新しいレイヤーになっていることが分かります。イメージに変更を加えたとき、yarnの依存関係が再インストールされたことを覚えているかもしれません。これを修正する方法はあるのでしょうか。ビルドするたびに同じ依存関係をインストールするなんて馬鹿げていますよね。
これを修正するには、Dockerfileを再構築して依存関係をキャッシュする必要があります。Nodeベースのアプリケーションでは、
package.jsonファイルで依存関係が定義されています。ということは、このファイルだけを最初にコピーしておけば、依存関係がインストールされてから他のファイルがコピーされます。なので、package.jsonを変更したときのみyarnの依存関係が再作成されるようになります。素晴らしいでしょう?1.Dockerfileを更新して、最初に
package.jsonのコピーが行われ、依存関係がインストールされ、それから他のファイルがコピーされるようにします。FROM node:12-alpine WORKDIR /app COPY package.json yarn.lock ./ RUN yarn install --production COPY . . CMD ["node", "src/index.js"]2.Dockerfileと同じフォルダに
.dockerignoreというファイルを作成し、以下の内容を書き込みます。node_modules
.dockerignoreファイルを使えば、イメージに必要なファイルだけを選択してコピーすることができます。詳しくはここを参照してください。この場合、node_modulesフォルダは2回目のCOPYで除外されます。さもなければ、RUNのコマンドで生成されたファイルで上書きされるでしょう。なぜこの方法がNode.jsアプリケーションで推奨されているかということと、他のベストプラクティスについての詳細は、DockerでのNode製Webアプリをご覧ください。3.
docker buildで新しいイメージをビルドします。docker build -t getting-started .以下のように表示されるはずです。
Sending build context to Docker daemon 219.1kB Step 1/6 : FROM node:12-alpine ---> b0dc3a5e5e9e Step 2/6 : WORKDIR /app ---> Using cache ---> 9577ae713121 Step 3/6 : COPY package.json yarn.lock ./ ---> bd5306f49fc8 Step 4/6 : RUN yarn install --production ---> Running in d53a06c9e4c2 yarn install v1.17.3 [1/4] Resolving packages... [2/4] Fetching packages... info fsevents@1.2.9: The platform "linux" is incompatible with this module. info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation. [3/4] Linking dependencies... [4/4] Building fresh packages... Done in 10.89s. Removing intermediate container d53a06c9e4c2 ---> 4e68fbc2d704 Step 5/6 : COPY . . ---> a239a11f68d8 Step 6/6 : CMD ["node", "src/index.js"] ---> Running in 49999f68df8f Removing intermediate container 49999f68df8f ---> e709c03bc597 Successfully built e709c03bc597 Successfully tagged getting-started:latest全てのレイヤーが再構築されているのがわかると思います。Dockerfileを大きく変更したので、もう問題ありません。
4.
src/static/index.htmlファイルを編集しましょう(<title>のところの内容を"The Awesome Todo App"に変更します)。5.もう一度
docker build -t getting-started .でDockerイメージをビルドします。今回は、表示される内容が少し変わって、以下のようになるはずです。Sending build context to Docker daemon 219.1kB Step 1/6 : FROM node:12-alpine ---> b0dc3a5e5e9e Step 2/6 : WORKDIR /app ---> Using cache ---> 9577ae713121 Step 3/6 : COPY package.json yarn.lock ./ ---> Using cache ---> bd5306f49fc8 Step 4/6 : RUN yarn install --production ---> Using cache ---> 4e68fbc2d704 Step 5/6 : COPY . . ---> cccde25a3d9a Step 6/6 : CMD ["node", "src/index.js"] ---> Running in 2be75662c150 Removing intermediate container 2be75662c150 ---> 458e5c6f080c Successfully built 458e5c6f080c Successfully tagged getting-started:latestビルドがとても早くなったのに気がついたでしょう。また、ステップ1-4には全て
Using casheが含まれているのが分かると思います。これで、ビルドキャッシュを使うことができました。イメージをプッシュし、プルし、更新するのがとても早くなります。マルチステージビルド
このチュートリアルではあまり深く触れませんが、マルチステージビルドは、複数のステージを使用してイメージを作成するのに役立つとんでもなくパワフルなツールです。以下のような利点があります。
ランタイムの依存関係とビルド時間の依存関係を分離できます。
アプリが起動するのに必要な分だけを配置することで、イメージ全体のサイズを減らします。
MavenとTomcatの例
Javaベースのアプリケーションを構築する際には、ソースコードをJavaバイトコードにコンパイルするのにJDKが必要になります。しかし、JDKは本番環境ではそのJDKは必要ありません。また、アプリをビルドするのにMavenやGradleのようなツールを使うかもしれません。これらも最終的なイメージには必要ありません。ここで、マルチステージビルドが役立ちます。
FROM maven AS build WORKDIR /app COPY . . RUN mvn package FROM tomcat COPY --from=build /app/target/file.war /usr/local/tomcat/webappsこの例では、
buildと名付けた1つ目のステージで、Mavenを用いたJavaのビルドを行います。FROM tomcatから始まる2つ目のステージで、buildステージからファイルをコピーします。最終的なイメージは、作成される最終ステージ(--targetフラグを使用するとオーバーライドされます)だけです。Reactの例
Reactアプリケーションをビルドするときは、JSコード(通常はJSX)、SASSスタイルシートなどを静的なHTMLとJS、CSSにコンパイルするNode環境が必要がです。サーバーサイドレンダリングが必要ない場合は、本番環境のビルドにNode環境は必要ありません。それなら、静的なNginxコンテナに静的なリソースを配置すればいいだけの話ですよね。
FROM node:12 AS build WORKDIR /app COPY package* yarn.lock ./ RUN yarn install COPY public ./public COPY src ./src RUN yarn run build FROM nginx:alpine COPY --from=build /app/build /usr/share/nginx/htmlここでは、
node:12イメージを使用してビルド(レイヤキャッシュの最大化)を行ってから出力をNginxコンテナにコピーしています。この方がいいでしょ?要約
イメージが構造化される方法を少し理解したことで、イメージのビルドは早くなり、より少ない変更を配置できるようになりました。また、マルチステージビルドは、イメージ全体のサイズを減らし、ビルドタイムの依存関係とランタイムの依存関係を切り離すことで最終的なコンテナのセキュリティを向上させるのに役立ちます。
次にすること
チュートリアルは終わったわけですが、コンテナについて学ぶことはもっとあります。ここで深く触れることはないですが、少しみてみましょう。
コンテナのオーケストレーション
本番環境でコンテナを動かすのは難しいことです。マシンにログインして
docker runやdocker-compose upを実行したいとは思いません。なぜでしょうか?では、コンテナが死んだら何が起こるでしょうか?複数のマシンにまたがってスケールする方法は?コンテナのオーケストレーションはこの問題を解決します。Kubernetes、Swarm、Nomad、ECSのようなツールは全てこの問題をわずかに異なる方法で解決します。予期される状態を受け取る「マネージャ」を持つというのが一般的な考えです。この状態というのは、「Webアプリのインスタンスを2つ起動して、80番ポートを開放してほしい」というようなものです。マネージャはクラスタ内の全てのマシンを監視し、作業を「ワーカ」に委任します。マネージャは(コンテナが停止された、のような)変更を監視し、それから実際の状態が予期された状態を反映するように動作します。
クラウドネイティブコンピューティング基盤プロジェクト
CNCF(クラウドネイティブ基盤のプロジェクト)は、ベンダーに依存しない様々なオープンソースプロジェクトで、Kubernetes、Prometheus、Envoy、Linkerd、NATSなどを含みます。作られたプロジェクトはこちら、全体的なCNCFの図はこちらで見ることができます。これらのたくさんのプロジェクトは、モニタリング、ロギング、セキュリティ、イメージレジストリ、メッセージングなどの問題を解決するのに役立ちます。
なので、コンテナの全容とクラウドネイティブアプリケーションの開発がよくわからない場合は、ぜひご利用ください。コミュニティに参加し、質問をして、勉強してみてください。あなたが参加してくれることを心待ちにしております。
- 投稿日:2020-09-13T17:16:53+09:00
build-push-actionを使って、GitHub Container Registryにイメージをpushしたい
Dockerイメージをレジストリに登録する作業を自動化したい場合、build-push-actionというワークフローを利用することが多いと思います。このワークフローを使って、2020/09/03にオープンベータになったGitHub Container Registryにイメージをpushしたい場合は、おおよそ以下のようにすればよいです。
name: Publish Docker image on: push jobs: publish_image: runs-on: ubuntu-latest steps: - name: Push to GitHub Container Registry uses: docker/build-push-action@v1 with: username: ${{ github.actor }} password: ${{ secrets.ACCESS_TOKEN }} registry: ghcr.io repository: example/exampleポイントは次の通りです。
registryはghcr.ioを指定します。usernameはレポジトリにアクセスできるユーザを指定してください。なおここではワークフロー実行ユーザ名を格納するgithub.actorを利用しています。passwordには、レポジトリへの読み取り・書き込み権限を有するアクセストークン(★)を指定する必要があります。(★) アクセストークンを作成し、Github Actions内で利用できるようになるまでの手順は以下の通りです。
- https://github.com/settings/tokens/new へアクセス。認証が求められる場合がある。
- アクセストークン作成画面では、
write:packagesとread:packagesに最低限チェックを付けて、Genereate Tokenを押下する。必要な場合はほかの権限も付与すること。- 作成したアクセストークンを控えておく
- Github Actionsを使うレポジトリに移動。
Settings > Secrets > New Secretの順番に移動。- Secret作成画面では、Name="任意の名前"(上記のサンプルでは
ACCESS_TOKEN)、VALUE="3.で控えておいたアクセストークン"を指定して、Add Secretを押下。- これでワークフロー内で
${{ secrets.任意の名前 }}という変数が利用できるようになります。
- 投稿日:2020-09-13T15:59:56+09:00
Laravel開発環境構築
Laravel-base
ネットを探すと色々あるのですが、個人的に一番しっくりくるLaravel開発環境です。
忘れないようにメモ的な意味でアップします。
DockerでNginx、PHP-FPM、MySQLを構成しています。
ペースにしているのは下記サイトに記載されていた方法です。ディレクトリやPHPのバージョンが違いますが、ほぼ同じやり方で記載しています。参考:
DigitalOcean - How To Set Up Laravel, Nginx, and MySQL with Docker Compose
https://www.digitalocean.com/community/tutorials/how-to-set-up-laravel-nginx-and-mysql-with-docker-composeGithubにもアップしています。
https://github.com/noktone/laravel-baseDockerのインストール
インストール
以下はUbuntu18.04での作業になります。Macの人はDocker for Macを、Windowsの人はDocker for Windowsをインストールしてください。
# 旧バージョンのDockerをアンインストール $ sudo apt-get remove docker docker-engine docker.io # aptのアップデート $ sudo apt-get update # HTTPSリポジトリを利用できるようにする $ sudo apt install apt-transport-https ca-certificates curl software-properties-common # GPGキーの追加 $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - # DockerリポジトリをAPTソースに追加 $ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" # インストール $ sudo apt-get update $ sudo apt-get install docker-ce docker-composeインストール後の処理
一般ユーザーでDockerを実行できるようにする
# ユーザーをdockerグループに追加 $ sudo groupadd docker $ sudo usermod -aG docker $USER # テスト $ docker run hello-worldエラーの一例
下記エラーが出る場合(idコマンドなどでdockerグループが表示されない場合です)があります。
docker: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post http://%2Fvar%2Frun%2Fdocker.sock/v1.39/containers/create: dial unix /var/run/docker.sock: connect: permission denied. See 'docker run --help'.dockerグループへログインして有効にします。
$ newgrp docker # プライマリのグループがdockerになるので、ユーザーのグループに戻す(hogeユーザーのhogeグループの場合) $ newgrp hogeLaravelのベースプロジェクトをGitから取得する
composerなどで作成してもよいですが、ここではgitから取得する方法で作成します。
また、ホームディレクトリにsrc/laravel-baseというディレクトリを作ることを前提にしています。適宜変更してください。$ mkdir -p ~/src/laravel-base $ cd ~/src/laravel-base # laravleの最新版をclone $ git clone https://github.com/laravel/laravel.git # 特定のバージョンを指定する場合は、cloneする際にバージョンを指定します。 $ git clone -v 5.5.28 https://github.com/laravel/laravel.gitComposer
$ cd ~/src/laravel-base/laravel $ docker run --rm -v $(pwd):/app composer installdocker runコマンドに--vオプションをつけることで現在のディレクトリをバインドマウントしたコンテナが作成されます。Windowsの人は「$(pwd)」のところに直接パスを書きます。--rmはDocker終了後に削除するオプションです。別にこの段階やらなくても大丈夫です。
Linuxの人はアクセス権の問題を回避するため、所有者をroot以外のユーザーに設定します。
$ sudo chown -R $USER:$USER ~/src/laravel-baseDocker Composeファイルの作成
mroongaのDockerイメージはrootパスワードやdatabaseを自動作成しませんが、ここではMySQLのDockerイメージを使用する場合に備えて記載しています。
PHPはDockerfileで指定するため、後ほど作成します。$ vim docker-compose.ymlversion: '3' services: #PHP Service app: build: context: . dockerfile: ./php/Dockerfile container_name: app restart: unless-stopped tty: true environment: SERVICE_NAME: app SERVICE_TAGS: dev working_dir: /var/www volumes: - ./laravel:/var/www - ./php/local.ini:/usr/local/etc/php/conf.d/local.ini networks: - app-network #Nginx Service webserver: image: nginx:alpine container_name: webserver restart: unless-stopped tty: true ports: - "80:80" - "443:443" volumes: - ./laravel:/var/www - ./nginx/conf.d/:/etc/nginx/conf.d/ networks: - app-network #MySQL Service db: image: mysql:8 container_name: db restart: unless-stopped tty: true ports: - "3306:3306" environment: MYSQL_DATABASE: laravel MYSQL_ROOT_PASSWORD: rootpassword MYSQL_USER: laravel MYSQL_PASSWORD: password TZ: 'Asia/Tokyo' SERVICE_TAGS: dev SERVICE_NAME: mysql volumes: - dbdata:/var/lib/mysql/ - ./mysql/log:/var/log/mysql - ./mysql/my.cnf:/etc/mysql/my.cnf networks: - app-network #Docker Networks networks: app-network: driver: bridge #Volumes volumes: dbdata: driver: localDockerfileの作成
PHP7.4-FPM用のDockerfileを作成します。
$ vim ~/src/laravel-base/php/DockerfileFROM php:7.4-fpm # Copy composer.lock and composer.json COPY ./laravel/composer.lock /var/www/ COPY ./laravel/composer.json /var/www/ # Set working directory WORKDIR /var/www # Install dependencies RUN apt-get update && apt-get install -y \ build-essential \ libpng-dev \ libjpeg62-turbo-dev \ libfreetype6-dev \ locales \ zip \ jpegoptim optipng pngquant gifsicle \ vim \ unzip \ git \ libonig-dev \ curl \ libzip-dev \ zlib1g-dev # Clear cache RUN apt-get clean && rm -rf /var/lib/apt/lists/* # node.js RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash - RUN apt-get install -y nodejs # Install extensions RUN docker-php-ext-install pdo_mysql zip exif pcntl RUN docker-php-ext-configure gd --with-freetype=/usr/include/ --with-jpeg=/usr/include/ RUN docker-php-ext-install gd # Install composer RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer # Add user for laravel application RUN groupadd -g 1000 www RUN useradd -u 1000 -ms /bin/bash -g www www # Copy existing application directory contents COPY . /var/www # Copy existing application directory permissions COPY --chown=www:www . /var/www # Change current user to www USER www # Expose port 9000 and start php-fpm server EXPOSE 9000 CMD ["php-fpm"]PHPの設定
PHP7.2-FPMの設定です。phpディレクトリを作成して、その配下にiniファイルを設置します。
$ mkdir ~/src/laravel-base/php $ vim ~/src/laravel-base/php/local.iniupload_max_filesize=40M post_max_size=40M date.timezone = "Asia/Tokyo"Nginxの設定
Nginxのコンフィグファイルを作成します。80版ポートで動作させてます。
$ mkdir -p ~/src/laravel-base/nginx/conf.d $ vim ~/src/laravel-base/nginx/conf.d/app.confserver { listen 80; index index.php index.html; error_log /var/log/nginx/error.log; access_log /var/log/nginx/access.log; root /var/www/public; location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass app:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } location / { try_files $uri $uri/ /index.php?$query_string; gzip_static on; } }動作時にタイムアウトが問題になる場合は下記 keepalive_timeout、send_timeout、fastcgi_read_timeout、fastcgi_connect_timeout、fastcgi_send_timeoutオプションを指定する(未検証。増やしすぎるとパフォーマンスの劣化につながるため注意)
server { listen 80; index index.php index.html; error_log /var/log/nginx/error.log; access_log /var/log/nginx/access.log; root /var/www/public; keepalive_timeout 600; send_timeout 600; location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass app:9000; fastcgi_index index.php; fastcgi_read_timeout 600; fastcgi_connect_timeout 600; fastcgi_send_timeout 600; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } location / { try_files $uri $uri/ /index.php?$query_string; gzip_static on; } }MySQLの設定
$ mkdir ~/src/laravel-base/mysql $ vim ~/src/laravel-base/mysql/my.cnfgeneral_logなどは状況によって。
[mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_bin general_log = 0 general_log_file = /var/lib/mysql/general.log log_timestamps=SYSTEM secure-file-priv=NULL default-authentication-plugin = mysql_native_password [mysql] default-character-set = utf8mb4 [client] default-character-set = utf8mb4ログ用のディレクトリも作成しておきます。
$ mkdir ~/src/laravel-base/mysql/logLaravelの環境設定ファイルの作成
Laravelの環境設定ファイルを作成します。環境設定ファイル(.env)はgitの管理下からは外しますので、本番環境では別途用意します。
$ cp .env.example .env $ vim .envlaravel-baseをgit cloneした場合は下記を参考にしてください。DB_HOSTにはデータベースのコンテナ名を記載します。
APP_NAME=Laravel APP_ENV=local APP_KEY= APP_DEBUG=true APP_URL=http://localhost LOG_CHANNEL=stack DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=laravel DB_USERNAME=root DB_PASSWORD= BROADCAST_DRIVER=log CACHE_DRIVER=file QUEUE_CONNECTION=sync SESSION_DRIVER=file SESSION_LIFETIME=120 REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 MAIL_MAILER=smtp MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS=null MAIL_FROM_NAME="${APP_NAME}" AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_DEFAULT_REGION=us-east-1 AWS_BUCKET= PUSHER_APP_ID= PUSHER_APP_KEY= PUSHER_APP_SECRET= PUSHER_APP_CLUSTER=mt1 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"Gitの管理下に含めないファイル
Docker用のファイルなど、アプリケーションに直接関係のないファイルはGitの管理下には置かないようにします。
$ vim .gitignore/node_modules /public/hot /public/storage /storage/*.key /vendor .env .phpunit.result.cache Homestead.json Homestead.yaml npm-debug.log yarn-error.log docker-compose.yml Dockerfileパッケージのインストール
下記を実行して、venderパッケージをインストールします。
$ docker-compose exec app php composer installDockerコンテナの作成とLaravelの起動時の設定
コンテナの作成と起動を実行します。
$ docker-compose up -d作成が終わったら、動作しているか確認します。
# 起動中のコンテナの確認 $ docker psLaravelのアプリケーションキーを生成します。
$ docker-compose exec app php artisan key:generate設定をキャッシュする場合は以下を実行します。設定内容が/var/www/bootstrap/cache/config.phpコンテナにロードされます。
$ docker-compose exec app php artisan config:cacheマイグレーションを実行し、データベース無いに認証機能に必要なテーブルを作成
$ docker-compose exec app php artisan migrate設定が完了したら、下記URLで確認します。
http://localhost
終了する場合は下記を実行します。$ docker-compose down # volumeごと消すとき(MySQLがうまく設定できないとか。データが消えるので注意) docker-compose down --volumes不調のときに試すもの。データが消えたりするので注意
# 止まってるコンテナ、使われてないボリューム、使われてないネットワーク、使われてないイメージを削除します。 $ docker system prune # 個別にやる場合は下記になります。 $ docker image prune $ docker container prune $ docker network prune $ docker volume pruneMySQLのDBとユーザーの作成
docker-composeで定義しているのですでに作成されているはずですが、やり方を忘れないように記載
$ mysql -u root -p --protocol=tcp$ CREATE DATABASE laravel; $ CREATE USER 'laravel'@'%' IDENTIFIED BY 'password'; $ GRANT ALL ON laravel.* TO 'laravel'@'%' WITH GRANT OPTION; $ FLUSH PRIVILEGES; $ exit
- 投稿日:2020-09-13T15:47:55+09:00
docker上のVue.jsのvue uiを使う
docker上にVue.jsのコンテナを立てたけど、
vue uiを使いたくなった。
デフォだとlocalhostかつ8000で立ち上がる。オプションを指定するだけ
非常に簡単
vue uiの後にいろいろオプションを足せる/usr/src/app$ vue ui -h Usage: ui [options] start and open the vue-cli ui Options: -H, --host <host> Host used for the UI server (default: localhost) -p, --port <port> Port used for the UI server (by default search for available port) -D, --dev Run in dev mode --quiet Don't output starting messages --headless Don't open browser on start and output port -h, --help output usage informationひとまず8080はdev用にのportとして置いていて、一応このデフォポートを使えるように8000もdocker-comose.ymlの時点で繋げた。
localhostだとdockerの外から見にいけないので、hostを
0.0.0.0にして起動する。$ vue ui -H 0.0.0.0 ? Starting GUI... ? Ready on http://0.0.0.0:8000これでhttp://localhost:8000/を見に行くと表示されている。
-P, —port でportを8080のすればそれでもいける。
以上、備忘録。
- 投稿日:2020-09-13T14:29:17+09:00
Stoneでポートフォワード
- 投稿日:2020-09-13T14:05:22+09:00
【RailsAPI×Docker】シェルで手軽に環境構築 & Flutterでも動作確認
初心者の方でも進められるよう手順は丁寧してあります。
やること
- RailsAPI × Docker での環境構築
- RailsAPIを軽く実装
- APIクライアントからAPIを叩く(PostmanやFlutterアプリから)※Flutterは任意で
APIコンテナ起動のシェルだけ見たい方 → こちら
一応 RailsAPIのソースコード、Flutterのソースコードも用意(※いじり倒してる可能性あり)手順
- API側のDockerコンテナ起動まで
- ディレクトリ作成
- シェルでRailsAPIコンテナビルド
- コンテナ起動&確認
- RailsAPI実装
- User モデル作成&DBテーブル用意
- User のコントローラ作成
- ルーティング設定
- PostmanでAPIの動作を確認
- Postmanインストール
GETでDBのUserリソースを取得POSTでDBのUserリソースを追加- FlutterでAPIを叩く
- プロジェクト作成
- Http通信を行えるようにする
- main.dartを編集
前提
- AndroidStudioでFlutterアプリケーションを起動した状態にする
- SDKなどをダウンロードしてシミュレータを起動できる
- ※ Flutterセットアップはこちらの記事で基本全部いけました(3~40分はかかります)
- Docker, docker-compose のコマンドが使える(
$ docker-compose -vが動けばOK)1. API側のDockerコンテナ起動まで
RailsAPIのDockerコンテナを作っていきます。
ただ、1からコマンドを打っていくのは非効率なので、基本的なところはシェルにまとめてあります。
1-1. ディレクトリ作成
app_nameにプロジェクト名を当てはめてディレクトリを用意します。$ mkdir app_name # APIアプリを置くディレクトリ用意 $ cd app_name1-2. シェルでRailsAPIコンテナビルド
以下がAPIコンテナのビルドまでやってくれるシェルです。(参考にした記事)
サンプルAPI立ち上げ用のシェル
set_up_rails.sh#!/bin/bash #config setting############# APP_NAME="app_name" # ← 自由に変更してください(ディレクトリ名と一緒がいいかも) MYSQL_PASSWORD="password" # ← 自由に変更してください ########################### echo "docker pull ruby2.6.6" docker pull ruby:2.6.6 echo "docker pull mysql:5.7" docker pull mysql:5.7 echo "docker images" docker images echo "make Dockerfile" cat <<EOF > Dockerfile FROM ruby:2.6.6 ENV LANG C.UTF-8 RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs #yarnのセットアップ RUN curl -o- -L https://yarnpkg.com/install.sh | bash ENV PATH /root/.yarn/bin:/root/.config/yarn/global/node_modules/.bin:\$PATH # 作業ディレクトリの作成、設定 RUN mkdir /${APP_NAME} ENV APP_ROOT /${APP_NAME} WORKDIR \$APP_ROOT # ホスト側(ローカル)のGemfileを追加する ADD ./Gemfile \$APP_ROOT/Gemfile ADD ./Gemfile.lock \$APP_ROOT/Gemfile.lock # Gemfileのbundle install RUN bundle install ADD . \$APP_ROOT # gem版yarnのuninstall rails6でエラーになるため RUN gem uninstall yarn -aIx #webpackerの設定 #RUN rails webpacker:install EOF echo "make Gemfile" cat <<EOF > Gemfile source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } gem 'rails', '~> 6.0.3.2' EOF echo "make Gemfile.lock" touch Gemfile.lock echo "make docker-compose.yml" cat <<EOF > docker-compose.yml version: '3' services: db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD} MYSQL_DATABASE: root ports: - '3306:3306' api: build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' volumes: - .:/${APP_NAME} ports: - '3000:3000' links: - db EOF echo "docker-compose run api rails new . --api --force --database=mysql --skip-bundle" docker-compose run api rails new . --api --force --database=mysql --skip-bundle echo "docker-compose run api bundle exec rails webpacker:install" docker-compose run api bundle exec rails webpacker:install docker-compose build # fix config/database.yml echo "fix config/database.yml" cat config/database.yml | sed "s/password:$/password: ${MYSQL_PASSWORD}/" | sed "s/host: localhost/host: db/" > __tmpfile__ cat __tmpfile__ > config/database.yml rm __tmpfile__ echo "docker-compose run api rails db:create" docker-compose run api rails db:createエディタからプロジェクト直下に新規ファイルとして作成&↑をコピペし、
set_up_rails.shなどとして保存しましょう。「自由に変更してください」となっているapp_nameなどは合わせて編集してください。保存が終わったら以下のようにシェルの権限を変更し、実行してみましょう(5分くらいかかります)。
$ chmod 755 set_up_rails.sh # 権限の変更 $ ./set_up_rails.sh # セットアップのシェル実行 docker pull ruby2.6.6 2.6.6: Pulling from library/ruby 57df1a1f1ad8: Pull complete 71e126169501: Pull complete 1af28a55c3f3: Pull complete ・ ・ ・シェルの実行が終わったら、
$ docker-compose psでコンテナの状態を確認してみましょう。以下のようになっていれば問題ないです。$ docker-compose ps Name Command State Ports ---------------------------------------------------------- app_name_db_1 docker- Up 0.0.0.0:3306-> entrypoint.sh 3306/tcp, mysqld 33060/tcp1-3. コンテナ起動 & 確認
次に、シェルでビルドしたコンテナを起動します。
$ docker-compose up rails-api-handson_db_1 is up-to-date Creating rails-api-handson_api_1 ... done Attaching to rails-api-handson_db_1, rails-api-handson_api_1 db_1 | 2020-09-12 08:27:00+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 5.7.31-1debian10 started. ・ ・ ・ api_1 | * Environment: development api_1 | * Listening on tcp://0.0.0.0:3000 api_1 | Use Ctrl-C to stop # ← これが出たらOK!Dockerコンテナのログが大量に出ますが、最後の
Use Ctrl-C to stopが出ればコンテナが起動しているのでOKです。コンテナの起動が確認できたので、http://localhost:3000 にアクセスし、ちゃんとRailsとしてレスポンスを返してくれるか確認しましょう。以下のようになっていればAPIコンテナの動作は大丈夫です。
2. RailsAPI実装
動作確認用なので単純なユーザデータにしましょう。今回は、
User ( id, name, email )みたいなリソースで実装してみます。
2-1. Userモデル作成 & DBテーブル用意
Userモデルとテーブルを用意します。
ここだけマイグレーションを使う方法とRidgepoleを使う方法に分けて紹介します(どちらも結果として同じモデル、スキーマが反映されます)。
Ridgepoleは、マイグレーションを使わずにスキーマを管理できるツールです。僕はこちらで進めていきますが、普段からマイグレーションを使っている方であればそちらで問題ありません。
パターン1) マイグレーションでモデル&テーブル作成
コンテナに入り、ジェネレータでUserモデルを作成。
$ docker-compose exec api bash # コンテナに入る # rails g model user name:string email:string # ↑ 出来なければ以後はBundle経由でやってみましょう( # bundle exec rails .... みたいな ) # exit # コンテナから出る(以後はコンテナの出入りは明記しません。 # コマンドラインの「$」や「#」で区別します。) $あとはマイグレーションをかけるだけです。
# rake db:migrateパターン2) Ridgepoleでモデル&テーブル作成
マイグレーションをスキップしてモデルを作成。
# rails g model user --skip-migrationプロジェクト直下にある
Gemfileにgem 'ridgepole', '>= 0.8.0'を追記します。mysql2の下とかで問題ないでしょう。Gemfilegem 'mysql2', '>= 0.4.4' # 元々ある gem 'ridgepole', '>= 0.8.0' # これを追加追記したgemをインストール。
# bundle install次に
db/のなかに、Schemafile.rbというファイルを作成します(Ridgepoleではスキーマをここで一元管理する)。
db/Schemafile.rbには以下のように書き、保存します。db/Schemafile.rbcreate_table "users", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t| t.string "name" t.string "email" t.timestamps endridgepoleのコマンドでスキーマファイルを反映。
# bundle exec ridgepole --config ./config/database.yml --file ./db/Schemafile.rb --apply Apply `./db/Schemafile.rb` -- create_table("users", {:options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8"}) -> 0.0716s2-2. User のコントローラ作成
こちらの記事を参考に、URIにAPIのメジャーバージョン(v1.3なら1の部分)を含めます。ただこの記事だと、URIに
apiというネームスペースを使っています。これは僕個人の意見ですが、URIに
apiと明記するよりも、サブドメインなどで示した方がいいのでは?と思います。APIを使おうとするクライアントからすればAPIだってことはわかっていますから。example.com/api/vi/users # URIでapiだと示す api.example.com/vi/users # サブドメインで示すでは本題のコントローラ作成に戻ります。
コントローラの前に、メジャーバージョンを示す
v1/を作成し、その中でusersコントローラを作成します。$ mkdir app/controllers/v1 $ rails g controller v1::usersコントローラファイルの中身は以下の感じです。
*/v1/users_controller.rbclass V1::UsersController < ApplicationController before_action :set_user, only: [:show, :update, :destroy] def index users = User.order(created_at: :desc) render json: { status: 'SUCCESS', message: 'Loaded user', data: users } end def show render json: { status: 'SUCCESS', message: 'Loaded the user', data: @user } end def create user = User.new(user_params) if user.save render json: { status: 'SUCCESS', data: user } else render json: { status: 'ERROR', data: user.errors } end end def destroy @user.destroy render json: { status: 'SUCCESS', message: 'Deleted the user', data: @user } end def update if @user.update(user_params) render json: { status: 'SUCCESS', message: 'Updated the user', data: @user } else render json: { status: 'SUCCESS', message: 'Not updated', data: @user.errors } end end private def set_user @user = User.find(params[:id]) end def user_params params.require(:user).permit(:name, :email) end end2-3. ルーティング設定
config/routes.rbを以下のようにします。config/routes.rbRails.application.routes.draw do namespace 'v1' do resources :users end end↑が終わると、
rake routesコマンドでルーティングが確認できるはずです。以下のようなルーティングが反映されていればOKです。# rake routes Prefix Verb URI Pattern Controller#Action v1_users GET /v1/users(.:format) v1/users#index POST /v1/users(.:format) v1/users#create v1_user GET /v1/users/:id(.:format) v1/users#show PATCH /v1/users/:id(.:format) v1/users#update PUT /v1/users/:id(.:format) v1/users#update DELETE /v1/users/:id(.:format) v1/users#destroy3. PostmanでAPIの動作を確認
Postmanとは、WebAPIのテストクライアントサービスのひとつです。APIの動作確認にはこれを使います。
3-1. Postmanインストール
公式サイトで、Postmanをインストールします。前にインストールしたのであまり覚えていませんが、登録して進めていけばOKだった気がします...。
Postmanインストール後、まずはコンソールで適当にUserのレコードを用意しておきましょう。以下のような感じでいいかと思います。
# rails c Loading development environment (Rails 6.0.3.3) irb(main):001:0> User.create(name: "hoge", email: "hoge@example.com")3-2.
GETでDBのUserリソースを取得Postmanで以下のように
GETメソッド、URIを指定し、Sendボタンを押します。すると、画面下のBodyで返ってきたレスポンスのBodyが確認できます。ここまでできていればAPIの動作確認としては成功です!
3-3.
POSTでDBのUserリソースを追加今度は以下のように、
POSTメソッドを指定し、リクエストBodyの中にもデータを含めてみましょう。この時、形式がJSONになっているか確認しましょう(画像の"JSON"とオレンジになっているところ)。成功すれば、登録されたUserのレコードがレスポンスBodyから確認出来ます。
ここで先ほどの
GETメソッドでのアクセスをもう一度行ってみると、POSTメソッドで追加したレコードも一緒に確認できます。4. FlutterでAPIを叩く
結果こんな感じ
- アプリを起動したらDBにあるユーザのデータを表示
- 右下の
Addボタンでユーザデータの登録動作確認なのでミニマムな感じです。
4-1. プロジェクト作成
File > New > New Flutter Project...からFlutterプロジェクトを作成します。iPhoneやAndroidのシミュレータ(エミュレータ)などが起動できていればOKです。4-2. Http通信を行えるようにする
デフォルトだとhttpを行うパッケージのimportでエラーを起こすので少し操作が必要になります。
まず、
pubspec.yamlのdependencies:に以下のように追記します。pubspec.yaml# ~ 省略 ~ dependencies: http: ^0.12.0 # ← 追加する flutter: sdk: flutter # ~ 省略 ~すると、Android Studioのエディタの上の方に、以下のようなFlutterコマンドが出てきます。今回は
Pub getを選択します。もしこの先の作業でHttp周りのエラーが出るようであれば、僕が参考にしたこちらの記事も見てみるといいかも知れません。
4-3. main.dartを編集
動作確認をしたいだけであれば、
lib/main.dartを以下のように書き換えれば動きます。なお、僕はFlutterは初歩の初歩なので物申すことがあればコメントにお願いします。
lib/main.dartimport 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; void main() => runApp(MyApp()); class MyApp extends StatefulWidget { MyApp({Key key}) : super(key: key); @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { Future<GetResults> res; @override void initState() { super.initState(); res = getUsers(); } // postUser()でUserを登録。setState内で再度User一覧を取得 void _postUser() { postUser(); setState(() { res = getUsers(); }); } @override Widget build(BuildContext context) { return MaterialApp( title: 'APIをPOSTで呼び出しJSONパラメータを渡す', theme: ThemeData( primarySwatch: Colors.yellow, ), home: Scaffold( appBar: AppBar( title: Text('RailsAPI x Flutter'), ), body: Center( child: FutureBuilder<GetResults>( future: getUsers(), builder: (context, snapshot) { if (snapshot.hasData) { return Text( snapshot.data.message.toString() ); } else if (snapshot.hasError) { return Text("${snapshot.error}"); } return CircularProgressIndicator(); }, ), ), floatingActionButton: FloatingActionButton( onPressed: _postUser, tooltip: 'Increment', child: Icon(Icons.add), ), ), ); } } class GetResults { final String message; GetResults({ this.message, }); factory GetResults.fromJson(Map<String, dynamic> json) { var datas = ''; json['data'].forEach((item) => datas += 'id: ' + item['id'].toString() + ', name: ' + item['name'] + ', email: ' + item['email'] + '\n'); return GetResults( message: datas, ); } } class PostResults { final String message; PostResults({ this.message, }); factory PostResults.fromJson(Map<String, dynamic> json) { return PostResults( message: json['message'], ); } } class SampleRequest { final String name; final String email; SampleRequest({ this.name, this.email, }); Map<String, dynamic> toJson() => { 'name': name, 'email': email, }; } Future<GetResults> getUsers() async { var url = 'http://127.0.0.1:3000/v1/users'; final response = await http.get(url); if (response.statusCode == 200) { return GetResults.fromJson(json.decode(response.body)); } else { throw Exception('Failed'); } } Future<PostResults> postUser() async { var url = "http://127.0.0.1:3000/v1/users"; // APIのURI var request = new SampleRequest(name: 'foo', email: 'foo@example.com'); // Userのパラメータ。自由に変更してOK final response = await http.post(url, body: json.encode(request.toJson()), headers: {"Content-Type": "application/json"}); if (response.statusCode == 200) { return PostResults.fromJson(json.decode(response.body)); } else { throw Exception('Failed'); } }シミュレータを起動すると、、登録されたUserがきちんと返ってきてますね!
右下のAddボタンを押すと、、
main.dartで指定したUserデータが登録され、取得できているのも確認できました!終わり
質問やご指摘があればコメントにお願いします。
- 投稿日:2020-09-13T13:23:03+09:00
Windows10 HomeにDokerをベースとした開発環境を構築する 2020 ver. その1 WSL2ベースのDocker構築まで
はじめに
最近開発をしてません。なんとなくWindowsパソコンを買いました。Windows 10 Homeつまりです。つまりHome開発環境を整えることが必然となりました。
どんな開発がしたいの?
数年前までMacを利用して以下のような開発をしていました。
・Webアプリケーション(フロント・サーバーサイド)
・AWSインフラ構築(コードで定義する)上記のような開発をWindowsで構築しようとするととにかく大変でした(私の記憶では)Rubyの導入から躓いたり、
Railsを動かしていても異常に動作が遅く使い物にならなかったりと、とにかくストレスでした。なので表題にあるMac並みというのは私がストレスなくWebアプリケーションの開発をローカル及びAWS上で実施できる環境を構築するということとなります。
どうやって実現するか
ざっとながら下調べをしまして、Dockerをベースとした開発環境が構築できればよいのかなとあたりをつけました。
しかしWindowsでは仮想環境の実行にHyper-Vが必要だったことを思い出し、Windows10 Homeである私にはもう無理だ・・・
と勝手に諦めかけていたとき「WSL2」と天の声が聞こえてきました。
実現方針は決まりました。
WSL2 → Doker → VSCode です。もう他に方法はありません。WSL2 をWindows10 Homeに導入
WSL2の導入可能なWindows2020年バージョンは2020年9月現在以下となります。
・「Windows 10 1903」および「Windows 10 1909」(2020年8月20日より導入可能となった)
・「Windows 10 2004」私のWindowsのバージョンは1909でしたので導入可能ですが、せっかくですので2004にアップデートした後
導入します。※ 2004でWLS2を導入する場合、2020年9月10日現在一部のセキュリティパッチの影響で不具合がでることが報告されています。
https://www.zdnet.com/article/windows-10-2004-patch-tuesday-problem-update-breaks-wsl2-say-users/
Hyper-Vが原因の一つとされていますので、Windows Homeでは影響しないことを信じて進めていきますWindows 10 2004 導入
現在のバージョンを確認するにはWinキー+Rで以下のコマンドを実行します。1909でした。
winverあとはWindows Updateです。私の場合は下のような画面でしたので、「ダウンロードしてインストール」を押してただひたすらに待ちます。
待っている間にWSL2導入手順の確認です。
「Windows 10 2004」からOSの標準機能となりましたが、標準機能となっただけで、機能を有効化しないと使用できません。有効化します。
「Windowsの機能の有効化または無効化」というそのままの機能があります。Winキー+Rで以下のコマンドを実行しますOptionalFeatures「Linux用Windowsサブシステム」と「仮想マシンプラットフォーム」にチェックし、インストールを実施します。
これでWSL2が導入されました。
Docker Desktop のインストール
以下のURLからDocker Desktopをインストールします
https://hub.docker.com/editions/community/docker-ce-desktop-windows以下のようにWSL2のサポートが有効になっていることを確認します。
インストールが完了すると以下のように「WSL2のLinuxカーネルは別のインストーラーでアップデートが管理されてるからインストールしてね」と案内がでますので従います。
WSL2Dを基盤としたDockerがこれで動作したようです。動作確認をするためダッシュボードに従って以下のコマンドをPowerShell上で実行してみます。
docker run -d -p 80:80 docker/getting-started80番ポートでなにやらサーバーがリッスンしているようなので http://localhost/tutorial/ へアクセスしてみます。
コンテナが動作しました。これでWindows 10 Homeでもdockerを使った開発ができるはずです。その2に続く。
- 投稿日:2020-09-13T12:58:54+09:00
GitHub Container Registry(ghcr.io)にdocker pushする
アドレスが長くて忘れるのでメモしておく.
docker tagコマンドの形式は以下に従う.
docker tag <コンテナ名> ghcr.io/<GitHubのユーザ名>/<パッケージ名>/<コンテナ名>:<タグ>例えば以下になる.
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE stns-build latest 449f1c2d3562 4 minutes ago 35.4MB $ docker tag stns-build:latest ghcr.io/tomoyk/stns-client/stns-client:v0.1 $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE stns-build latest 449f1c2d3562 6 minutes ago 35.4MB ghcr.io/tomoyk/stns-client/stns-client v0.1 449f1c2d3562 6 minutes ago 35.4MB最後にPushする.
docker push ghcr.io/tomoyk/stns-client/stns-client:v0.1弾かれる場合は
~/.docker/config.jsonを見てみる.
authsの下にghcr.ioがあるか確かめる.{ "auths": { "ghcr.io": { "auth": "xxxx" } } }ない場合は,GitHubのWeb UIからPersonal Access Tokenを発行する.
Tokenを
~/TOKEN.txtとして保存して以下のコマンドを実行する.cat ~/TOKEN.txt | docker login https://ghcr.io -u <ユーザ名> --password-stdin再び
docker push xxxを実行する.
- 投稿日:2020-09-13T12:05:33+09:00
Docker × MySQL8でエラー「MySQL said: Protocol mismatch; server version = 11, client version = 10」
DockerでMySQL8を構築して、TablePlusで接続しようとするとタイトルのエラーが出た。
一応環境
Docker version 19.03.12
docker-compose version 1.24.0
mysql Ver 8.0.21 for Linux on x86_64 (MySQL Community Server - GPL)原因
どうやらクライアント側のポートを
33060にするとエラーが起きるようだった。
ということで13306に変更してみたところ、接続ができた。// docker-compose.yml // 変更前 ports: - 33060:3306 // 変更後 ports: - 13306:3306特に他に書くこともないので、以上!
- 投稿日:2020-09-13T10:18:40+09:00
Poetry Django Docker Pycharm で開発環境構築する
はじめに
pythonを始めて1年経ちますが、Pythonの環境構築はまだ慣れません。
特にパッケージマネージャは数ある言語の中でも最弱のように感じます。
なぜかmacで開発環境を構築しようとするとなぜか OpenSSL がらみのエラーが発生していました。そんな悩みの解決に開発環境をdocker上に構築する方法をまとめました。
ソースは Github にあげてあります。
ディレクトリ構成
├── pyproject.toml └── docker └── Dockerfile └── docker-compose.yml └── mysite/*ファイル
メインどころのファイルのみ記述します。
Djangoが作成したファイルは置いておきます。pyproject.toml[tool.poetry] name = "python-dev-on-docker" version = "0.1.0" description = "" authors = ["va034600"] [tool.poetry.dependencies] python = "^3.6" django = "^3.1.1" [tool.poetry.dev-dependencies] [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api"DockerfileFROM python:3.6.8 WORKDIR /usr/src/app ENV POETRY_VERSION=1.0.10 \ PATH="/root/.poetry/bin:$PATH" RUN curl -sSL https://raw.githubusercontent.com/sdispater/poetry/${POETRY_VERSION}/get-poetry.py | python && \ poetry config virtualenvs.create false COPY ./pyproject.toml /usr/src/app/pyproject.toml RUN poetry install CMD tail -f /dev/nulldocker-compose.ymlversion: '3.5' services: app: build: context: ../ dockerfile: ./docker/Dockerfile working_dir: /usr/src/app/mysite command: python manage.py runserver 0.0.0.0:8000 volumes: - ../mysite:/usr/src/app/mysite ports: - "8000:8000"consoleからdocker-composeを実行
とりあえず、Pycharmなしの
docker-compose upで動作を確認します。$ cd docker $ docker-compose up -d $ curl http://127.0.0.1:8000/ $ docker-compose down hello1Pycharm でデバッグ
Pycharnの設定
Python Interpreter はdocker-composeで追加する
プロジェクトのInterpreterをdocker-composeにする
Configuration で runserverを作成する
ポイントは パラメータで
runserver 0.0.0.0:8000にすること
Pycharm でデバッグ実行する
これでブレークポイントも止まります。
ファイルを修正してdocker-composeの再起動なく反映もされます。終わりに
これでホスト側がすっきり。
簡単に開発環境が構築できます。
- 投稿日:2020-09-13T04:41:39+09:00
Docker始めました
作ったツール類をコンテナにまとめてしまえばいろいろ便利と耳にしたので、以前から気になっていたDockerを使い始めることにしました。
今回は、pythonのflaskで作ったwebサーバをコンテナ化し、コンテナの起動・停止だけでwebサーバを管理できるようにしてみようと思います。使用した環境はこちら
$ cat /etc/redhat-release Red Hat Enterprise Linux release 8.2 (Ootpa) $ docker --version Docker version 19.03.12, build 48a66213fe※ 余談ですが、CentOS 8でDockerをインストールしようとすると
containerd.ioが無いぞって言われることがあるようです。私の場合はこちらで無事解決できましたので参考までに。Dockerおさらい
まずはDockerの使い方(コンテナの作り方)について簡単に整理します。Dockerコンテナの作り方は次の4ステップ。
- ベースとなるイメージをpull
- Dockerfile作成
- Dockerイメージのビルド
- イメージからDockerコンテナの生成・起動
個人的に、最初はDockerfileを作成するところが一番よくわからないところだと思います。なので、まずは第一段階としてpythonの簡単なスクリプトをコンテナ内で動かすところまで作り、第二段階としてflask製webサーバを動かすところまで改良していきます。
コンテナ内でpythonスクリプトの実行
第一段階として、コンテナ内で次の
myapp.pyを実行するところまで作ります。myapp.pyprint("hello Docker!")ベースとなるイメージをpull
最初にpython3系の実行環境となるイメージを用意します。Docker Hubに登録されているpython3系のイメージを検索。
$ sudo docker search python3 NAME DESCRIPTION STARS OFFICIAL AUTOMATED rackspacedot/python37 11 sellpy/python3-jupyter-sklearn python3-jupyter-sklearn 5 [OK] openwhisk/python3action Apache OpenWhisk runtime for Python 3 Actions 5 sellpy/python3-jupyter-sklearn-java python3-jupyter-sklearn-java 2 [OK] quoinedev/python3.6-pandas-alpine Python 3.7 on alpine with numpy and pandas i… 2 (略)今回はSTARSが最も多い
rackspacedot/python37を使用します。これをpullしましょう。$ sudo docker pull rackspacedot/python37
rackspacedot/python37もDockerイメージなので、pullした後はdocker imagesで確認できます。$ sudo docker images REPOSITORY TAG IMAGE ID CREATED SIZE rackspacedot/python37 latest 3d51361ee118 7 weeks ago 1.06GBこのイメージがあればコンテナ内でpythonが使えるようになるので、これをベースとしてその他の必要な部分(スクリプトの配置など)を追加すればOKという感じです。
Dockerfile作成
次に、
Dockerfileという名前のファイルを作成します。FROM rackspacedot/python37 WORKDIR /usr/local/script COPY myapp.py . CMD python myapp.py簡単に説明しますと、
FROM rackspacedot/python37
- ベースとなるイメージを指定する部分です。先ほどpullしたイメージを指定します。
WORKDIR /usr/local/script
- コンテナ内での作業ディレクトリを指定します。今自分がいるディレクトリと違っていても問題ありません。
COPY myapp.py .
- myapp.pyをコンテナ内の第二引数で指定したディレクトリにコピーします。今回は
.で指定しているので、WORKDIRで指定したディレクトリにコピーされます。CMD python myapp.py
- コンテナ起動時に実行するコマンドです。
ここまでで用意したファイルは次の通り。
$ ls -l total 8 -rw-r--r--. 1 user developer 146 Sep 11 16:39 Dockerfile -rw-r--r--. 1 user developer 173 Sep 11 17:04 myapp.pyDockerイメージのビルド・コンテナ起動
Dockerfileを作成したので、イメージのビルド→コンテナ起動をしていきます。
まずはイメージのビルドから。$ sudo docker build -t myapp:test .
-t 名前:タグはイメージの名前とタグを設定します(スクリプト名と違っていてもOKです)。.はDockerfileがあるディレクトリのパスを指定します。
Successfully built ハッシュ値と出ればイメージが生成されたので、docker imagesで確認できるようになります。$ sudo docker images REPOSITORY TAG IMAGE ID CREATED SIZE myapp test e577339515a7 10 seconds ago 1.06GB rackspacedot/python37 latest 3d51361ee118 7 weeks ago 1.06GBイメージができたので、コンテナを作成・起動します。
$ sudo docker run myapp:test hello Docker!無事コンテナでpythonスクリプトを実行できました。
ここで起動したコンテナは残るので、再度同じコマンドで実行することができます。また、docker ps -aで作成したコンテナを確認することができます。コンテナ内でflask製サーバの実行
さて、コンテナ内でpythonスクリプトを実行できるようになったので、第二段階としてflaskでサーバを作成していきましょう。
myapp.pyを次のように書き換えます。from flask import Flask app = Flask("myapp") @app.route("/") def main(): return "hello Docker!!" if __name__ == "__main__": app.run(host="0.0.0.0", port=8080)一応先ほどと変化がわかるように
!の数を2個にしました。
これをコンテナ内でwebサーバとして使える状態にしましょう。Dockerファイル修正
flaskを使用するので、コンテナ内にflaskをインストールする必要があります。
rackspacedot/python37にはデフォルトでpipが入っていますので、イメージのビルド時にpip installする部分をDockerfileに追加します。FROM rackspacedot/python37 RUN pip install flask RUN useradd docker USER docker WORKDIR /usr/local/script COPY myapp.py . CMD python myapp.py追加した分を説明すると、
RUN pip install flask
- コンテナ内にflaskをインストールします。
RUN useradd docker / USER docker
dockerというユーザーを追加し、その後の操作をdockerユーザーとして実行します。- 今回は無くても動作しますが、セキュリティ上の理由で追加しています。
Dockerイメージのビルド・コンテナ起動
ファイルを書き換えたので、イメージを再ビルド→コンテナ起動します。
まずは再ビルドから。$ sudo docker build -t myapp .今回はタグを省略したので、自動的に
latestというタグが割り当てられます。docker imagesで確認してみましょう。$ sudo docker images REPOSITORY TAG IMAGE ID CREATED SIZE myapp latest 6c747be5d597 3 seconds ago 1.06GB myapp test e577339515a7 50 minutes ago 1.06GB rackspacedot/python37 latest 3d51361ee118 7 weeks ago 1.06GBそれではコンテナを起動しましょう。webサーバなので、オプションを追加しています。
$ sudo docker run -d -p 8080:8080 myappポート番号ですが、
-p 受付ポート番号:コンテナ側の番号の書き方になります。受付ポート番号は何番でも良いのですが、コンテナ側の番号はコンテナ内のmyapp.pyで指定した番号(今回の場合は8080)にします。問題なく起動していれば、
docker psで起動中のコンテナを確認することができます。$ sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 00d1ab6fc24a myapp "/bin/sh -c 'python …" 3 seconds ago Up 2 seconds 0.0.0.0:8080->8080/tcp thirsty_mccarthy実際にアクセスしてみましょう。
$ curl http://localhost:8080 hello Docker!!いいですね。これでwebサーバのコンテナ化完了です。
コンテナを停止したい場合は$ sudo docker stop thirsty_mccarthy停止後に再度起動したい場合は
$ sudo docker start thirsty_mccarthy
thirsty_mccarthyの部分はコンテナ名(docker ps実行時のNAMEに表示されるもの)を指定してください。docker startで起動すると、自動的にdocker runの時に指定したポートで実行してくれます。
また、コンテナ化しておくと作ったものを他の環境で実行する時もイメージのビルド→コンテナ起動ですぐに動かせるので、非常に便利ですね。今回は以上になります。Docker Composeで複数コンテナをコマンド一つで実行できるようになればさらに幅が広がるので、そのうちこれも勉強しようかなと思います。

























































