20200310のdockerに関する記事は19件です。

Win10 HomeのWSL2+DockerでGUIのROS1/ROS2をセットアップする方法

@karaage0703さんが「Docker上でGUIのROS1/ROS2を一瞬でセットアップする方法」にて紹介してくださったDockerイメージ(tiryoh/ros-desktop-vnctiryoh/ros2-desktop-vnc)がWindows 10 Home上のWSL2で問題なく動いたので紹介します。

Image from Gyazo

使い方(簡略版)

DockerがインストールされたWindows 10で以下のコマンドを実行し、Webブラウザでhttp://127.0.0.1:6080にアクセスすると、Dockerコンテナ内にアクセスできるVNCクライアントが立ち上がります。

docker run -p 6080:80 --shm-size=512m tiryoh/ros2-desktop-vnc:dashing

環境

OS

使用したOSはWindows 10 Home Insider Previewのバージョン2004、OSビルドは19564.1000です。

Image from Gyazo

WSL2のインストールについては@aki4000さんの「Windows10 HomeとWSL2でdocker-composeができるようにする」が大変わかりやすかったです。

Docker

使用したDocker Desktop for Windowsのバージョンは2.2.2.0(43066)です。

Image from Gyazo

使い方

DockerがインストールされたWindows 10で以下のコマンドを実行します。

docker run -p 6080:80 --shm-size=512m tiryoh/ros2-desktop-vnc:dashing

初回起動時はWindowsのセキュリティ警告が出ました。

Image from Gyazo

アクセスを許可するとDockerコンテナ内でサーバが立ち上がるのを確認できます。

Image from Gyazo

Webブラウザでhttp://127.0.0.1:6080にアクセスすると、VNCクライアント経由でターミナルを立ち上げたり、各種の操作ができます。Gazeboも起動できます。

Image from Gyazo

Image from Gyazo

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

【RSpec】System Specが動作するRails+Docker開発環境を構築する

はじめに

Rspec3.7以降で実装された"System Spec"を動作できるRails+Docker環境の作り方をまとめました。

基本コピペで進められるように書きましたので、初学者の方であっても「まず動く」状態まで持っていけると思います。

この記事では、Docker、docker-composeがすでにインストールされた状態からスタートします。私は、自PC(Mac)のホストOSに「Docker for Mac」をインストールしています。

著者動作環境における、Docker、docker-composeのバージョンは以下の通りです。

$ docker --version
Docker version 19.03.5, build 633a0ea
$ docker-compose -v
docker-compose version 1.25.4, build 8d51620a

DockerでのRails環境の構築

まずは、Docker公式が用意してくれているRals用のクイックスタート「Quickstart: Compose and Rails」をベースに、Railsが動作するDocker環境を作成していきます。

一部、公式のファイル内容に変更を加えていますので、ご注意ください。

早速始めていきましょう。作業ディレクトリ用意し、cdで移動します。

$ mkdir sample_app
$ cd sample_app

Quickstartに則り、sample_app内に下記5ファイルを作成します。

/sample_app
$ touch Dockerfile docker-compose.yml Gemfile Gemfile.lock entrypoint.sh
ディレクトリ構造
/sample_app
 ├── Dockerfile
 ├── Gemfile
 ├── Gemfile.lock
 ├── entrypoint.sh
 └── docker-compose.yml

各ファイルを下記の通り編集します。5ファイルのうち、docker-compose.yml以外は、Quickstart公式のコピペです。vim等、お好きなテキストエディタをお使いください。

Dockerfile
FROM ruby:2.5
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]
Gemfile
source 'https://rubygems.org'
gem 'rails', '~>5'
Gemfile.lock
(空のまま)
entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
docker-compose.yml
version: '3'
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    environment:
      - "POSTGRES_USER=xxxx" # 追記
      - "POSTGRES_PASSWORD=xxxx" # 追記
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db
    environment:
      - "SELENIUM_DRIVER_URL=http://selenium_chrome:4444/wd/hub" # 追記
  selenium_chrome:
    image: selenium/standalone-chrome-debug # 追記

上記5ファイルのうち、docker-compose.ymlのみ、編集で手を加えています。具体的には、
・postgresにユーザー名、パスワードを指定
・System Specを動作させるためのコンテナイメージ「selenium/standalone-chrome-debug」を追加

ファイルを作成したら、Railsアプリを新規作成します。

$ docker-compose run web rails new . --force --no-deps --database=postgresql
/sample_app
$ ls
Dockerfile              README.md               bin                     db                      lib                     public                  tmp
Gemfile                 Rakefile                config                  docker-compose.yml      log                     storage                 vendor
Gemfile.lock            app                     config.ru               entrypoint.sh           package.json            test

rails newに伴い、Gemfileにデフォルトのgemがインストールされました。

Gemfileに編集を加えます。具体的には、

・"rspec-rails"を追加
・"chromedriver-helper"を削除

Gemfile
# 省略

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'rspec-rails' # 追記
end

group :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'
  # Easy installation and use of chromedriver to run system tests with Chrome
  # gem 'chromedriver-helper' # 削除
end

#省略

Gemfileを更新したため、再ビルドします。

$ docker-compose build

これで、新しいGemfileでbundle installが実行されました。

続いて、configディレクトリ内のdatabase.ymlを編集します。

config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: xxxx
  password: xxxx
  pool: 5

development:
  <<: *default
  database: myapp_development


test:
  <<: *default
  database: myapp_test

DBを作成します。

$ docker-compose run web rails db:create
Starting sample_app_db_1 ... done
Created database 'myapp_development'
Created database 'myapp_test'

コンテナを起動します。このタイミングで、selenium/standalone-chrome-debugのプルが実行されます。

$ docker-compose up -d
Pulling selenium_chrome (selenium/standalone-chrome-debug:)...
latest: Pulling from selenium/standalone-chrome-debug
.
.
.

イメージプルが終了しましたら、docker-composeが正常に立ち上がっているか、確認してみましょう。

$ docker-compose ps
            Name                           Command               State           Ports         
-----------------------------------------------------------------------------------------------
sample_app_db_1                docker-entrypoint.sh postgres    Up      5432/tcp              
sample_app_selenium_chrome_1   /opt/bin/entry_point.sh          Up      4444/tcp, 5900/tcp    
sample_app_web_1               entrypoint.sh bash -c rm - ...   Up      0.0.0.0:3000->3000/tcp

web,db,selenium_chromeの3つが立ち上がっていれば、OKです。ついでに、http://localhost:3000 にアクセスし、いつもの「Yay! You’re on Rails!」が表示されているか、確認しましょう。

また、RSpecが正確にインストールされているかも確認してみましょう。

$ docker-compose run web rspec -v
Starting sampleApp_db_1 ... done
RSpec 3.9
  - rspec-core 3.9.1
  - rspec-expectations 3.9.0
  - rspec-mocks 3.9.1
  - rspec-rails 3.9.0
  - rspec-support 3.9.2

各バージョンは状況により異なるかと思いますが、RSpec 3.7以上のバージョンであれば、System Specを動作させることができます。

RSpecの初期設定

無事、必要なgemが揃った状態のDockerコンテナを用意できたので、ここからは中身の諸々の設定をいじっていきます。

まず、Railsにデフォルトで配置されているtestディレクトリは使用しないため、削除してしまいましょう。

$ rm -rf test

RSpecをインストールします。

$ docker-compose run web rails generate rspec:install

これにより、以下のディレクトリ構造に対して、計3ファイルが生成・配置されます。

ディレクトリ構造
/sample_app
  ├── .rspec
  └── spec
       ├── rails_helper.rb
       └── spec_helper.rb

.rpsecファイルを編集します。2行目のコードを追記してください。これにより、テスト結果の表示形式が変更されます。

.rspec
--require spec_helper
--format documentation

デフォルトでは、”失敗したテスト”のみがターミナル上に出力されるのですが、この変更を行うことにより、”成功・失敗両方のテスト”の結果を出力してくれるようになります。必須ではありませんが、設定しておくことをオススメします。

続いて、spec/rails_helper.rbについて、25行目あたりに書かれている下記コードのコメントアウトを外してください。

spec/rails_helper.rb
# 省略

Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

# 省略

これにより、後ほど定義するCapybara用の設定ファイルを読み込めるようになります。

specファイルの自動生成機能OFF

デフォルトでは、controllerやviewを作成したタイミングで、対応するspecファイル(=テストコードを書くためのファイル)が自動生成されます。

今回は初学者にとっての混乱を防ぐため、自動生成はOFFにしておきます。config/application.rbに下記コードを追加します。

config/application.rb
equire_relative 'boot'

require 'rails/all'

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module Myapp
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.2

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.

    ### 追記 ######################################
    config.generators do |g|
      g.test_framework :rspec,
                       view_specs: false,
                       helper_specs: false,
                       controller_specs: false,
                       routing_specs: false,
                       request_specs: false
    end
    ##############################################
  end
end

Capybaraの初期設定

Capybaraに設定を加えるため、capybara.rbファイルを作ります。

$ mkdir spec/support
$ touch spec/support/capybara.rb
spec/support/capybara.rb
require 'capybara/rspec'

RSpec.configure do |config|
  config.before(:each, type: :system) do
    driven_by :selenium, using: :headless_chrome, options: {
      browser: :remote,
      url: ENV.fetch("SELENIUM_DRIVER_URL"),
      desired_capabilities: :chrome
    }
    Capybara.server_host = 'web'
    Capybara.app_host='http://web'
  end
end

これで、SystemSpecが動作する環境が整いました。

System Spec実践

それでは実際に、静的ページを用いてSystemSpecを使用してみましょう。

今回は、

・rootページにアクセスし、"Hello World!"が表示されていることを検証
・rootページに、"Helpページ"へのリンクが置かれていることを検証
・リンク先のHelpページに、"This is the help page."が表示されていることを検証

という流れのテストを書いてみたいと思います。

下準備として、home,helpを持ったStaticPagesコントローラーを作成します。

$ docker-compose run web rails generate controller StaticPages home help

ルートとビューをいじります。

config/routes.rb
Rails.application.routes.draw do
  root 'static_pages#home'
   get '/help', to: 'static_pages#help'
end
app/views/static_pages/home.html.erb
<h1>StaticPages#home</h1>
<p><%= link_to "Help", help_path %></p>
app/views/static_pages/help.html.erb
<h1>This is the help page.</h1>

下準備が終わったため、specファイルを書いていきます。specディレクトリ下にsystemディレクトリを作成し、その中に"homes_spec.rb"を配置します。

$ mkdir spec/system
$ touch spec/system/homes_spec.rb

spec/system/homes_spec.rbを編集します。

spec/system/homes_spec.rb
require 'rails_helper'

RSpec.describe 'Home', type: :system do
  it 'shows greeting' do
    # root_pathへアクセス
    visit root_path
    # ページ内に'Hello World!'が含まれているかを検証
    expect(page).to have_content 'Hello World!'
    # 'Help'文字列をクリック
    click_on 'Help'
    # ページ内に'This is the help page.'が含まれているかを検証
    expect(page).to have_content 'This is the help page.'
  end
end

いよいよ動かします!今回用意した環境でSystem Specを動かすためには、webコンテナ内に入る必要があります。

$ docker-compose exec web bash

rspecは、"rails spec"コマンドで実行できます。

webコンテナ内
$ rails spec
/usr/local/bin/ruby -I/usr/local/bundle/gems/rspec-core-3.9.1/lib:/usr/local/bundle/gems/rspec-support-3.9.2/lib /usr/local/bundle/gems/rspec-core-3.9.1/exe/rspec --pattern spec/\*\*\{,/\*/\*\*\}/\*_spec.rb

Home
Capybara starting Puma...
* Version 3.12.4 , codename: Llamas in Pajamas
* Min threads: 0, max threads: 4
* Listening on tcp://web:45873
  shows greeting

Finished in 2.33 seconds (files took 8.35 seconds to load)
1 example, 0 failures

無事、テストが通ればOKです!!

スクリーンショット保存機能

ちなみにですが、System Specには、「失敗時の画面をスクリーンショットで自動保存する」という便利機能があります。

ビューをいじって、わざとテストを失敗させてみましょう。

app/views/static_pages/home.html.erb
<h1>StaticPages#home</h1>
<p><%= link_to "Hel", help_path %></p>  # "Help"をタイポしてしまったケース
webコンテナ内
$ rails spec

.
.
.

Home
Capybara starting Puma...
* Version 3.12.4 , codename: Llamas in Pajamas
* Min threads: 0, max threads: 4
* Listening on tcp://web:44173
  shows greeting (FAILED - 1)

Failures:

  1) Home shows greeting
     Failure/Error: click_on 'Help'

     Capybara::ElementNotFound:
       Unable to find link or button "Help"

     [Screenshot]: tmp/screenshots/failures_r_spec_example_groups_home_shows_greeting_612.png
.
.
.

「'Help'が見つかりません!」とメッセージが出され、tmp/screenshotsにエラーが発生したタイミングでのブラウザ画面が保存されています。

failures_r_spec_example_groups_home_shows_greeting_612.png

"Help"と書いたつもりのところが"Hel"になってしまっているのが、確認できます。

最後に

以上です!不明な点、間違っている点などがありましたら、私のTwitter(@ddpmntcpbr)のDMまで連絡頂けると幸いです。

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

「Kubernetes実践入門」でKubernetesの知識をキャッチアップされる方へ

先日、書籍「Kubernetes実践入門」でKubernetesの門を叩きました。

ステップバイステップで本格的な構成に近づけていく流れとなっており、個人的にはこの書籍でかなりkubernetesのことを理解できました。

具体的には、簡単なKubernetesクラスタの構築から始まり、セキュリティの設定やFluentd/Elastcsearch/Kibanaでのログ集約環境の構築、Prometheus/Grafanaでのメトリクス監視といったところまで学べます。

高額な研修などを受講しなくても、この書籍で学習すれば、右も左も分からないという状態からは脱して、現場で「もがける」状態になれる。そんな感覚を持ちました。

ただ、中にはスムーズにいかないところもありました。書籍「Kubernetes実践入門」でKubernetesに入門される方に、私と同じような苦しみを回避していただきたいと思い、この記事を書いてみました。

なお、本記事にはmac(macOS Catalina)でKubernetes(minikube)を動作させる場合の問題についてのみ記載されています。他のOSでは試しておりません。

私の個人的なメモも含まれますが、参考になればと思います。

全体を通して

書籍のソースコード

https://github.com/kubernetes-practical-guide/examples

トラブルシューティングのためにPodの状態を確認する方法

書籍に書いてある通りにやっているはずなのに、うまく動かない・・・ということが何度もあると思います。そんな場合に参考にしてください。

以下のコマンドでは、各Podの定義情報に加え、コンテナ生成などのイベント履歴を確認できます。イベント履歴にエラーの状況や原因が記録されていることがあります。

$ kubectl describe pods

Pod上(コンテナ上)のアプリのログを確認するには、

$ kubectl get pod

でPodのコンテナ名を確認してから、

$ kubectl logs <Podのコンテナ名>

で、Podのログを確認しましょう。

この時、以下のような仕組みで、ログがコマンドラインに出力されます。

アプリが標準出力/標準エラーにログを出力する。

このログを、Dockerランタイムがノードにログファイルとして保存する。

kubectl logs <Podのコンテナ名>を実行すると、Kubernetes APIサーバーが対象ノードのkubeletを呼び出す。

ノードのkubletがログファイルを読み出し、Kubernetes APIサーバーにログファイルの内容を返す。

コマンドラインにログファイルの内容が出力される。

Podのコンテナにログインして、コンテナ内部でコマンドを実行したい場合は、以下のとおりです。

$ kubectl exec -it <Podのコンテナ名> /bin/bash

Podが起動するのを待つときは、以下のコマンドで状況をウォッチすると良いです。

$ kubectl get pod -w

うまくいかないときは・・・

うまくいかないときは、原因の切り分けが難しくなるので、関連するオブジェクトを削除して作り直した方が良いです。

第1章 Hello Kubernetes world! コンテナオーケストレーションとKubernetes

特に困ることはありませんでした。とっても分かりやすいです。

第2章 Kubernetesを構築する

私のmac(macOS Catalina)では、kubectlとminikubeのインストールで少し手間取りました。

書籍ではHomebrewを使った手順が紹介されていますが、実際にはエラーが発生して使えませんでした。エラー内容は理由は省略しますが、結論としては解決は無理そうでした。

そこで、Kubernetes公式サイトの手順どおりインストールしました。具体的には以下の通りです。

kubectrlのインストール手順

書籍で指定されているバージョン(v1.11.3)をインストールする場合、手順は以下の通りです。

$ curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.11.3/bin/darwin/amd64/kubectl

$ chmod +x ./kubectl

$ sudo mv ./kubectl /usr/local/bin/kubectl

$ kubectl version --client

公式サイト:https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-on-macos

minikubeのインストール手順

書籍で指定されているバージョン(v0.28.2)を取得する場合、VirtualBoxをインストールした後、以下の手順を実施します。

$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.28.2/minikube-darwin-amd64

$ chmod +x minikube

$ sudo mkdir -p /usr/local/bin/

$ sudo install minikube /usr/local/bin/

$ minikube version

公式サイト:https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-on-macos

第3章 Kubernetes上にアプリケーションをデプロイする

この書籍のメインとなる章です。この章だけで全ページの25%くらいを占めており、ものすごく長いです。

特に分かりづらかったところについて、私の理解を記載させていただきます。

「3.5 クラスタ内のアプリケーション間で通信する」

Headless Service

何と言うか、直感的に理解しづらかったのがHeadless Serviceという概念でした。

普通のServiceはClusetIPというIPアドレスを持っていて、外部からClusterIPにアクセスされると、Serviceは配下のPod達に処理を振り分けます(ロードバランシング)。

一方、Headless Serviceは、ClusterIPを持たず、外部からDNSリクエストされると、配下のPod達のIPアドレスの一覧を呼び出し元に返します。

参考:https://cstoku.dev/posts/2018/k8sdojo-09/

「Pod名.Service名」というホスト名と、そのPodのIPアドレスがDNSに登録されます。

つまり、普通のServiceは「頭(head)」として配下のPod達にロードバランシングするのですが、Headless Serviceには、そういう「頭(head)」が無い、ということだと理解しました。

Headless Serviceの呼び出し元が、Headless Service配下の特定のPodにリクエストを送りたい場合に使われるようです。たとえばMySQLをMaster/Slave構成で構築する場合には、どのPodがMasterで、どのPodがSlaveかを呼び出し元が意識する必要があります。書籍では、Headless Serviceのこのような特徴を利用して、MySQLのMaste/Slave構成を構築しています(StatefulSetはHeadless Serviceを使って実現される、と解釈しました)。

「3.6 アプリケーションを外部に公開する」

NodePort

私は、書籍の説明だけでは「NodePort」なるものの概念を理解できませんでした。

簡単に言うと、特定のポート番号(これがNodePort)に送られてきた外部リクエストを、そのポート番号に紐づけられたNodePortサービスにkubernetes内部で転送する、というものらしいです。

具体的に言いますと、外部からのリクエストは以下の経路を辿ります。

  • クライアントがHTTPリクエストを送信

 ↓(送信)

  • kubernetesクラスターのIPアドレス:NodePortのポート番号(NodePortサービスのyaml項目:nodePort) でリクエストを受ける。

 ↓(転送)

  • NodePortサービスのIPアドレス:同サービスのポート番号(NodePortサービスのyaml項目:port

 ↓(転送)

  • NodePortサービスが管理するPodのIPアドレス:同Podのポート番号(NodePortサービスのyaml項目:targetPort

Podで動作するアプリを、かなりお手軽に外部に公開できるそうですね。しっかりとしたセキュリティが要求される場合には適さないと思いますが、ライトな用途であれば良さそうです。

Ingress

Ingressについても書籍の説明だけではよく分かりませんでした・・・。

IngressはServiceの一種ではありません(yamlでkind:Ingressと指定します)。
Serviceの前に配置される、クラスター内部のロードバランサーです。

こちら(15ページ目)の図が直感的には分かりやすいと思います。
https://www.slideshare.net/nobu0001/kubernetes-119605097

Ingressは以下の機能を持ちます。

  • ルーティング
    アクセスされるホスト名と、転送先のServiceの組み合わせを定義しておきます。その定義に従って、特定のホスト名に送られたリクエストを、特定のServiceに転送します。これにより、ルーティングを実現します。

  • 負荷分散
    複数ノードに存在するServiceに、リクエストを振り分けます。これにより、負荷分散を実現します。

外部ロードバランサー

NodePortやIngressとは別に、外部ロードバランサーという概念があります。(Ingressも外部に置けるらしいですが・・)

こちら(14ページ目)の図が直感的には分かりやすいと思います。
https://www.slideshare.net/nobu0001/kubernetes-119605097

ただ、Ingressや外部ロードバランサーも、NodePortの仕組みを使っているように見えますが、どうなんでしょうか。また、分かったときに追記したいと思います。

「3.7.3 ストレージの準備」でのNFSサーバー構築手順

macOSにバンドルされているNFSサーバーを利用する手順があるのですが、macOS Catalinaではうまくいきませんでした。

書籍には以下の手順が記載されています。

$ sudo mkdir /share
$ sudo chmod 777 /share
$ minikube ip
192.168.99.100
$ sudo vi /etc/exports
/share -mapall=nobody:wheel -network 192.168.99.0 -mask 255.255.255.0

しかし、このまま実行しようとすると、以下のとおりうまくいきませんでした。

$ sudo mkdir /share
mkdir: /share: Read-only file system

macOS Catalinaからファイルシステムが変更されたため、こうなってしまうようです。

※詳しくは以下をご覧ください。大変参考になります。
 https://applech2.com/archives/20190610-read-only-system-volume-apfs-refresh.html

そこで、NFSとして共有するディレクトリを、Catalinaのファイルシステムのルールに従って設ける必要があります。具体的には以下の2点です。

ポイント1

NFSの共有ディレクトリを、ホームディレクトリの直下あたりにつくりましょう(例:~/nfs_share)。

~/Documentsなどの配下に作ってしまうと、ルール違反となってしまうようです。

参考:https://www.firehydrant.io/blog/nfs-with-docker-on-macos-catalina/

その結果、Podのmysql-1でinit-slave.shが実行される時に、/mnt/backupにDBダンプを出力しようとするのですが、Permission deniedとなり、Podmysql-1がスレーブとして正常に起動しません。

一通り構築した後の動作確認で悲しいことになりますので、ご注意ください。

ポイント2

macOS側の/etc/exportsと、kubernetes側のmysql-pv.yamlにはNFS共有ディレクトリのパスを指定しますが、先頭に/System/Volumes/Data/を付けましょう。

例:/System/Volumes/Data/Users/your_user_name/nfs_share

上記に従って書籍を読み替えますと、以下のようになります。

macOSのターミナル
$ mkdir ~/nfs_share
$ chmod 777 ~/nfs_share
$ minikube ip
192.168.99.100
$ sudo vi /etc/exports
/System/Volumes/Data/Users/your_user_name/nfs_share -mapall=nobody:wheel -network 192.168.99.0 -mask 255.255.255.0
mysql-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: backup
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs
  mountOptions:
    - hard
  nfs:
    # ★★★こちらです!★★★
    path: /System/Volumes/Data/Users/your_user_name/nfs_share
    server: 192.168.99.1

nfsのPVC(PersistentVolumeClaim)を削除する方法

nfsのPVC(PersistentVolumeClaim)を誤って作成してしまい、削除したい場合は、Protectionを外す必要があります。以下の記事が大変参考になりました。
https://qiita.com/devs_hd/items/8cbf834c504e57fbe1ff

StatefulSetのボリュームの削除

Master/Slave構成のMySQLを構築するときに、うまくいかなくてやり直そうとしました。その際、MySQLのデータファイルを保存するボリュームを削除できず、困りました。(テーブルを再度作ろうとすると、ERROR 1050 (42S01) at line 1: Table 'test' already exists と出てしまいました。)

具体的には、単にkubectl delete -f mysql-sts.yamlとしてStatefulSetを削除しただけでは、Podが削除されるだけで、マウントされているボリュームは削除されませんでした。

そこで、

$ kubectl get pvc

# 上記コマンドで調べたpvc名を使って、pvcを削除する。
$ kubectl delete pvc {pvc名}

とすると、削除されました。PersistentVolumeClaim(pvc)を削除すると、それに紐付くPersistentVolume(pv)も併せて削除されました。つまり、kubectl delete pv {pv名}は不要でした。

参考:https://kubernetes.io/ja/docs/tasks/run-application/delete-stateful-set/

第8章 アプリケーションを運用する

EFKスタックの構築(Elasticsearch, Fluentd, Kibana)

結論から言いますと、物理メモリ8Gの私のmacでは、efkは動きませんでした・・・。

おまけ(書籍に登場するコマンドたち)

私の復習用です。簡単なコマンドは省略していますので100%ではないですが、書籍中の80%くらいのコマンドについては記載されているはずです。

長すぎてタイプミスしやすいものもありますので、コピペ用途などで使ってください。

第3章 Kubernetes上にアプリケーションをデプロイする

$ kubectl create deployment mattermost-preview --image k8spracticalguide/mattermost-preview:4.10.2

$ kubectl expose --type NodePort --port 8065 deployment mattermost-preview

$ minikube service mattermost-preview

$ kubectl get event -w -o custom-columns=KIND:.involvedObject.kind,NAME:.metadata.name,SOURCE:.source.component,REASON:.reason,MESSAGE:.message

$ kubectl create deployment dive-mattermost-preview --image k8spracticalguide/mattermost-preview:4.10.2 --v=8

$ kubectl get deploy,rs,pod -v=6 2>&1 | grep -e dive-mattermost -e https

$ kubectl get po -w | grep -e dive-mattermost -e NAME

$ kubectl scale rs $(kubectl get rs|grep dive-mattermost|awk '{print $1}') --replicas=1 -v=6 2>&1 | grep -e mattermost -e https

$ kubectl delete po --all -v=6 2>&1 | grep DELETE

$ kubectl get rs $(kubectl get rs|grep dive-mattermost|awk '{print $1}') -o template --template='{{.spec.selector.matchLabels}}'

$ kubectl get po $(kubectl get po|grep dive-mattermost|head -n 1|awk '{print $1}') -o template --template='{{.metadata.labels}}'

$ kubectl edit po $(kubectl get po|grep dive-mattermost|head -n 1|awk '{print $1}')

$ kubectl get po {直前のコマンドの実行結果に表示される、PodのID} -o template --template='{{.metadata.labels}}'

$ kubectl get po | grep -e dive-mattermost -e NAME

$ kubectl edit po {直前のコマンドの実行結果に表示される、PodのID}

$ kubectl get rs dive-mattermost-preview-7785c477c9 -o template --template='{{.metadata.ownerReferences}}'

$ kubectl delete deploy dive-mattermost-preview --cascade=false -v=8

$ kubectl create deploy mattermost --image nyandora/mattermost:4.10.2 -o yaml --dry-run > mattermost-deploy.yaml

$ kubectl create deploy db --image k8spracticalguide/mysql:5.7.22 -o yaml --dry-run > db-deploy.yaml

$ kubectl create cm common-env -o yaml --dry-run --from-literal MYSQL_USER=myuser --from-literal MYSQL_PASSWORD=mypassword --from-literal MYSQL_DATABASE=mattermost > cm.yaml

$ curl -L -O https://raw.githubusercontent.com/kubernetes-practical-guide/examples/master/ch3.4.2.2/config.json

$ kubectl create secret generic common-env -o yaml --dry-run --from-literal MYSQL_ROOT_PASSWORD=rootpassword --from-literal MYSQL_PASSWORD=mypassword > secret.yaml

# applyはcreateと違い、初回作成だけでなく変更にも使える。
$ kubectl apply -f .

$ kubectl logs $(kubectl get po | grep mattermost | awk '{print $1}')

$ kubectl get po -o wide

$ kubectl run test1 -i --rm --image k8spracticalguide/busybox:1.28 --restart=Never -- ping -c 1 172.17.0.7

# Labelセレクタで、app=dbに該当するPodの情報を取得。
$ kubectl get po -l app=db

$ kubectl apply -f db-service.yaml 

$ kubectl get svc,ep mattermost-db

$ kubectl run -i --rm test2 --image=k8spracticalguide/busybox:1.28 --restart=Never -- nslookup mattermost-db

$ kubectl create svc externalname ext-mattermost-db --external-name example.com

$ kubectl get svc,ep ext-mattermost-db -o wide

$ kubectl run -i --rm test4 --image=k8spracticalguide/busybox:1.28 --restart=Never -- nslookup headless-test

$ kubectl expose --type NodePort --port 8065 deploy mattermost --dry-run -o yaml > mattermost-service.yaml

$ kubectl apply -f mattermost-service.yaml

$ kubectl get svc mattermost -o wide

$ minikube ip

$ curl http://$(minikube ip):$(kubectl get svc mattermost -o jsonpath="{.spec.ports[0].nodePort}")

$ minikube addons enable ingress

$ kubectl get deploy -n kube-system -w

$ kubectl apply -f mattermost-ingress.yaml 

$ curl http://chat.$(minikube ip).nip.io

$ kubectl run -ti --image k8spracticalguide/busybox:1.28 dns-test --restart=Never --rm /bin/sh

$ kubectl run mysql-client --image=k8spracticalguide/mysql:5.7.22 -i --rm --restart=Never -- \
mysql -h mysql-0.mysql --user=root --password=rootpassword <<EOF
CREATE TABLE mattermost.test (msg VARCHAR(64));
INSERT INTO mattermost.test VALUES ('hello');
EOF

$ kubectl run mysql-loop --image=k8spracticalguide/mysql:5.7.22 -ti --rm --restart=Never -- \
/bin/bash -ic "while sleep 1; do mysql -h mysql-read --user=root --password=rootpassword \
-e 'SELECT @@server_id, msg from mattermost.test'; done"

第5章 アプリケーションを更新する

$ kubectl create serviceaccount my-service
$ kubectl get sa
$ kubectl run -it --rm --restart=Never --serviceaccount "my-service" --image k8s.gcr.io/hyperkube-amd64:v1.13.3 kubectl

sh-4.4# env | grep KUBERNETES_SERVICE
sh-4.4# ls /var/run/secrets/kubernetes.io/serviceaccount/

$ kubectl create rolebinding my-service-edit --clusterrole edit --serviceaccount default:my-service

$ kubectl get rolebinding -o wide

sh-4.4# kubectl run mynginx --image k8spracticalguide/nginx:1.15.5
sh-4.4# kubectl delete deploy mynginx
sh-4.4# kubectl get pods -n kube-system

$ kubectl delete rolebinding my-service-edit

$ kubectl create clusterrolebinding my-service-view --clusterrole view --serviceaccount default:my-service

sh-4.4# kubectl get pods -n kube-system
sh-4.4# kubectl get deploy,pod,svc --all-namespaces
sh-4.4# kubectl get secret

$ kubectl create rolebinding my-service-edit --clusterrole edit --serviceaccount default:my-service --dry-run -o yaml

$ kubectl get sa my-service -o yaml
〜〜略〜〜
secrets:
- name: my-service-token-6q87b
$ kubectl get secret my-service-token-6q87b -o yaml

第6章 アプリケーションの安定性をあげる

$ kubectl get pod -l app=mattermost -o wide -w &
$ kubectl get endpoints -l app=mattermost -w &
$ kubectl apply -f mattermost-deploy.yaml

$ kubectl get pod -l app=mysql -o wide -w &
$ kubectl get endpoints -l app=mysql -w &
$ kubectl apply -f mysql-sts.yaml 

$ kubectl scale deployment mattermost --replicas 3
$ kubectl get po -l app=mattermost

$ minikube addons enable metrics-server

$ kubectl get pod -n kube-system -l k8s-app=metrics-server

$ kubectl run cpu-max --image=k8spracticalguide/busybox:1.28 --requests=cpu=50m --limits=cpu=100m -- dd if=/dev/zero of=/dev/null

$ kubectl autoscale deployment cpu-max --cpu-percent=70 --min=1 --max=10

$ kubectl get horizontalpodautoscaler
$ kubectl describe hpa cpu-max

第7章 アプリケーションのセキュリティを強化する

$ minikube delete
$ minikube start --extra-config=kubelet.network-plugin=cni --kubernetes-version=v1.11.3
$ kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"

$ kubectl get deployment -n kube-system
$ sudo kubectl port-forward -n kube-system deployment/nginx-ingress-controller 80

$ kubectl run -it --rm --image="k8spracticalguide/busybox:1.28" --restart=Never test -- wget -T5 "http://mattermost:8065"

$ kubectl label namespace kube-system system=true

第8章 アプリケーションを運用する

$ minikube ssh
$ sudo find /var/log/pods | grep mattermost
efk
$ minikube delete
$ minikube start --kubernetes-version=v1.11.3 --memory 5012
$ kubectl apply -f .
$ minikube addons enable efk
$ kubectl get po -n kube-system -w
$ kubectl logs -n kube-system kibana-logging-8ldj4
$ kubectl describe pod -n kube-system kibana-logging-6v9st
Prometheus
$ minikube addons enable metrics-server
$ kubectl top node
$ kubectl top pod

$ minikube start --kubernetes-version=v1.11.3 --extra-config=kubelet.authentication-token-webhook=true

$ minikube service prometheus

$ kubectl exec -it mysql-0 /bin/bash
# mysql -u root -p
mysql> CREATE USER 'exporter'@'%' IDENTIFIED BY 'exporterpassword' WITH MAX_USER_CONNECTIONS 3;
mysql> GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'%';

$ echo -n "exporter:exporterpassword@(mysql.default.svc.cluster.local:3306)/" | base64
Grafana

PromQL
sum(mysql_info_schema_table_size{schema="mattermost"}) by (table)

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

docker-composeのコマンドをよく使う順にまとめる

めっちゃ使う

コンテナの起動

$ docker-compose up -d

コンテナの停止

$ docker-compose stop

コンテナ内でコマンドを実行

$ docker-compose exec {サービス名} {コマンド}
$ docker-compose exec web rails db:migrate
$ docker-compose exec web /bin/bash

けっこう使う

削除

コンテナ・ネットワーク・イメージ・ボリュームを停止して削除します。DBとか全部消える。

$ docker-compose down
$ docker-compose down --rmi all #イメージも全部消す

ビルド

Dockerイメージからコンテナを作ります。1回目の起動前にやる。削除の後とか。

$ docker-compose build

再起動

たぶんstopstartと同じ

$ docker-compose restart

ログの確認

$ docker-compose logs
$ docker-compose logs -ft #ログをリアルタイムで垂れ流す

たまに使う

コンテナの一覧を表示

$ docker-compose ps

イメージの一覧を表示

$ docker-compose images

参考

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

Docker Composeにおける各種ファイルの変更時の反映

はじめに

  • ここでは、docker-composeによるコンテナ構成時の、各種ファイル更新時の反映方法についてまとめる。
  • 以下のファイルが対象。
    • docker-compose.yaml
    • Dockerfile
    • ソースコード等

docker-compose.yaml変更を反映

  • イメージ:再構築の必要なし。
  • コンテナ:再構築の必要ありdocker-compose up -d

Dockerfileの変更を反映

  • イメージ:再構築の必要ありdocker-compose build
  • コンテナ:再構築の必要ありdocker-compose up -d

ソースコード等の変更を反映

  • イメージ:再構築の必要ありdocker-compose build
  • コンテナ:再構築の必要ありdocker-compose up -d
  • 以下、補足
    • ローカルのボリュームとコンテナのボリュームをマウントしてる場合はこれに限らず。 (コンテナに対してローカル領域をマウントしているのでイメージを作り直す必要はない)
    • Dockerfileの中でローカルのソースコードを読み込んでいる処理がある場合は、イメージの再構築が必要。 (イメージ内でソースコードを抱えているため、ローカル側のソースコードの修正が反映しないので、作り直す必要がある)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ローカル環境にSentryを立ち上げる

概要

Sentry On-Premise がdocker-composeを導入してくれたおかげで、セットアップがだいぶラクになっていた。
楽すぎて書くまでもないくらいなのだけれども、立ち上げるまでをメモしておく。

手順

0. 前提

  • Dockerのメモリが 2400MB 以上必要
  • Dockerの初期設定だとこれに引っかかってうまく起動できない場合があるので、割り当てメモリをチェックしておく
  • Docker Desktopの場合、 [Settings] -> [Advanced] -> [Memory] の順で確認可能
  • 少し多めに割り当てておくのがオススメ。2000MB前後になっている場合、3000MBにするとか

1. Sentry On-Premise をダウンロードする

  • Git管理するほどのものじゃないのでzipダウンロードで良いと思います。

2. configファイルを更新する

  • 追加設定が必要な場合のみ。 この手順は省略しても起動させられる。
  • onpremise/sentry の中にある下記ファイルをコピーして examle を削除することで、設定を上書きできる
    • config.example.yml
      • GitHub連携、通知メールサーバー変更、ファイルストレージ変更など
    • requirements.example.yml
      • 追加プラグインライブラリを導入する場合はここ
    • sentry.conf.example.yml
      • DB・Redis・Cache設定など

3. install.sh を起動する

  • すべての面倒くさい手順をこの install.sh がやってくれるようになりました
    • ただ時間はかかる…
  • このスクリプトのログは /onpremise-master 直下に吐き出される
    • スクリプトが終了し、かつ、ログの末尾に↓が表示された場合、正常に準備が完了している
----------------
You're all done! Run the following command to get Sentry running:

  docker-compose up -d

トラブルシューティング

  • Dockerの割り当てメモリが足りない場合は↓のログとともにFAILするので、0の手順を再確認すること
Checking minimum requirements...
FAIL: Expected minimum RAM available to Docker to be 2400 MB but found 1980 MB

4. docker-compose up -d 実行

  • ルートディレクトリで up を実行する
  • ブラウザから見られるようになるまでに少し時間が必要になる。3分くらい待つと :ok_woman:

5. ブラウザで localhost:9000 にアクセス

image.png

  • 正常に見られれば :tada:
  • この後のプロジェクト設定などは他記事をご参照ください
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Windows] Dockerを使用してホスト環境を汚さずにAngularの開発環境を構築する

TL;DR

  • Node.jsの開発環境をDockerをまとめたかった
  • ローカル環境にはDocker Desktopとvisual studio codeのみインストール
  • Angularでng newから書いている記事がなかったので書いた。これをベースに開発環境を育てていってほしい

環境

  • Windows10
  • Docker Desktop 2.2.0.3
  • docker-composeはDocker Desktopに同梱
  • visual studio code 1.42.1[Dockerの拡張機能を使用]

完成したリポジトリ

https://github.com/MegaBlackLabel/angular-docker-sample

ファイル

\---node-angular-sample
│  .gitignore
│  docker-compose.yml
│  docker-entrypoint.sh
│  Dockerfile
Dockerfile
FROM node:lts

COPY docker-entrypoint.sh ./
COPY ./frontend ./

RUN npm install -g @angular/cli

WORKDIR /frontend

EXPOSE 4200

ENTRYPOINT ["/docker-entrypoint.sh"]
docker-compose.yml
version: '3'

services:
  node:
    build: .
    environment:
        NG_CLI_ANALYTICS: "ci"
        NG_CLI_ANALYTICS: "false"
    ports:
        - "4200:4200"
    volumes:
        - "./frontend:/frontend"
        - nodemodules:/frontend/node_modules
    tty: true

volumes:
    nodemodules:
        driver: "local"
  • マウントしているfrontendについては初回起動時に作成する
  • NG_CLI_ANALYTICS、NG_CLI_ANALYTICSはnpm ci実行時に途中で確認メッセージが出て止まらないようにするために設定
docker-entrypoint.sh
#start SQL Server, start the script to create/setup the DB
#!/bin/bash

FILE="/frontend/package.json"
if [ -e $FILE ]; then
  npm ci
fi

/bin/bash
  • package.jsonがあるときはnpm ciを実行する
  • /bin/bashを実行してコンテナが停止しないようにする

マウントするフォルダの作成

docker-composeに記載しているホストのマウント先のフォルダを作成(無いとdocker-compose起動時にエラー発生)
mkdir frontend\node_modules

docker-composeビルド

コンテナのビルドを実施
docker-compose build

docker-compose起動

ビルドしたコンテナをバックグラウンドで起動
docker-compose up -d

Angularのプロジェクト作成

起動しているコンテナにアタッチしてコマンド実行
cd ..
ng new frontend --skipGit=true

※、これでfrontendフォルダにAngularプロジェクトが作成されます

Angular起動

外部から見えるようにhostを設定します
# attach shell
ng serve --host=0.0.0.0

docker-compose終了

コンテナを初期化して終了
docker-compose down -v

Angularプロジェクト作成後のフォルダ構成(.git内は省略)

\---node-angular-sample
│  .gitignore
│  docker-compose.yml
│  docker-entrypoint.sh
│  Dockerfile
│
└─frontend
    │  .editorconfig
    │  .gitignore
    │  angular.json
    │  browserslist
    │  karma.conf.js
    │  package-lock.json
    │  package.json
    │  README.md
    │  tsconfig.app.json
    │  tsconfig.json
    │  tsconfig.spec.json
    │  tslint.json
    │
    ├─e2e
    │  │  protractor.conf.js
    │  │  tsconfig.json
    │  │  
    │  └─src
    │          app.e2e-spec.ts
    │          app.po.ts
    │
    ├─node_modules
    └─src
        │  favicon.ico
        │  index.html
        │  main.ts
        │  polyfills.ts
        │  styles.scss
        │  test.ts
        │
        ├─app
        │      app.component.html
        │      app.component.scss
        │      app.component.spec.ts
        │      app.component.ts
        │      app.module.ts
        │
        ├─assets
        │      .gitkeep
        │
        └─environments
                environment.prod.ts
                environment.ts

git cloneして使う場合

初回時にマウント先のフォルダを作成
mkdir frontend\node_modules
docker-compose build

まとめ

この構成だとホストにNode.jsをインストールしないで初めからDockerの開発環境で進められる。
ただし、作成済みのプロジェクトをgit cloneして使う際にはnode_modulesを作成する必要があるので注意。

以上

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

Docker Hub pushメモ

docker pullないしdocker builddocker-compose build)で引っ張ってくる/構築するビルド済みコンテナ(コンテナイメージ)のホスティングハブ。ビルド時間をダウンロード時間に変えられる(階層別キャッシュあり)。プロジェクトがコンパクト&ポータブルになる。

  • アカウントを作成(初回のみ)
  • 新規リポジトリを作成(USERNAME/IMAGE_NAME)
  • タグ名を指定してビルド(-t, --tag
    • docker build --tag USERNAME/IMAGE_NAME .
  • Docker Hubにログイン(初回のみ)
    • docker login
  • Docker Hubにアップロード(push)
    • docker push USERNAME/IMAGE_NAME
  • 確認
    • docker pull USERNAME/IMAGE_NAME
    • DockerfileFROM USERNAME/IMAGE_NAME
    • docker-compose.ymlimage: USERNAME/IMAGE_NAME

相当な容量食うだろうに無料しゅごい...(無料の場合プライベートは1リポジトリのみ、料金体系)。

参考

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

Docker on Linux でホストPCのホスト名を取得する

ブリッジネットワーク使用時、Windows, Macでは、コンテナ内からhost.docker.internalが解決でき、それでホストPCと繋げられるそうです。

が、なぜかLinuxでは、20200310現在、その機能がありません。そのうちできるようになりそうな気配はありますが・・・。

それまでは、この方法で、コンテナ立ち上げ時にホストIPを渡せば良いみたいです。

docker run -it --add-host=host.docker.internal:$(ip route | grep docker0 | awk '{print $9}') debian bash
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker の コンソールが固まる。フリーズ・停止して動かない。それ Control + S のせいなら Control + Q で解除できない? ( #Mac #Docker )

よく固まるなーーと思って、毎回しかたなくウィンドウを閉じてたら、どうやら端末ロック機能のせいだったぽい。

キーを入力しても何も起きなくなる

image

Control+Q を押した瞬間、入力が解除される

image

環境: Mac + Docker

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3013

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

MacBookProでDockerを導入する

MacBookProでdockerを導入して、
・イメージをプルしてくる
・環境を編集する
・イメージをプッシュする(docker-hubに、ECRに)

<参考>
https://qiita.com/kurkuru/items/127fa99ef5b2f0288b81

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

DB接続がない状態でassets:precompileを行う

本番とは違う環境だったり、Dockerfile内でassets:precompileを行ったりするときにDB接続でエラーになるときがある。これを回避する方法ってあるのかなと思ったので調べてみた

activerecord-nulldb-adapterを使う

github
https://github.com/nulldb/nulldb

gem 'activerecord-nulldb-adapter'
config/database.yml
default: &default
  adapter: <%= ENV['DB_ADAPTER'] ||= "mysql2" %>

database.ymlに環境変数でDB_ADAPTERを指定する。

$ DB_ADAPTER=nulldb bundle exec rake assets:precompile

上記を実行すればDB接続なしでprecompileできる

参考

Rails × ECS でオートスケーリング&検証環境の自動構築
https://tech.medpeer.co.jp/entry/2018/06/20/080000

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

Pythonアプリケーションにおすすめなコンテナイメージ

Pythonアプリケーションの実行環境としてどのDockerイメージを選べばいいのか、迷ったことはありませんか。自分は毎回悩んでから公式イメージに落ち着くという判断になりがちだったので、ここではっきりさせておこうと思い比較してみることにしました。皆さんの参考になれば嬉しいです。

イメージ選択の評価項目

イメージを選ぶとき、以下の観点があると思います。

観点 内容
安定性 頻繁にファイル構成や内容が変わるようでは長期的な使用は難しいため、ある程度の安定性が必要です(あまりないとは思いますが)。
そのため、公式に近いイメージを使いたいところです。
イメージサイズ 一番定量的に評価できるのがイメージのサイズになります。これは小さければ小さいほどありがたいですね。
セキュリティのアップデート セキュリティのアップデートはOSのディストリビューションごとに異なりますが、いつまでサポートしているのかを確認する必要があります。
Pythonがインストール済みか 自分でインストールすることも可能ですが、元からインストールされているほうが手間とビルド時の時間を節約できます。

メジャーなディストリビューションの場合

標準的なディストリビューションを使用すれば安定性とセキュリティアップデートのサポート期間も確認できるため非常に安定しています。
しかし、Pythonのアプリケーションを実行するには不要な共通パッケージも多く含まれており、イメージサイズが大きくなりがちです。また、Pythonの最新バージョンは自身でインストールする必要があります。

Alpine Linuxの場合

イメージを小さくするならalpine-linuxが真っ先に候補に上がるのではないでしょうか。しかし、以下のリンクの記事によるとalpineを使用することで最終的にイメージの容量増加やビルド時間の増大など様々な問題が発生してしまうようです。
Using Alpine can make Python Docker builds 50× slower

記事で言及されている内容をまとめると以下のようになります。

  • pipでPyPIのライブラリをインストールする際、エラーが起きてしまう可能性がある。alpineではGNUバージョンのCライブラリではなくmusl Cライブラリが使用されているのが原因。
  • GNUバージョンで使用できるbinary形式のものがPyPIではメジャーだが、muslの環境で使用する場合は用意されているbinaryを使用できず、ほとんどコンパイルし直さなければならない。そのためビルド時間が伸びてしまう。
  • musl Cライブラリを使用していることによる予想し辛いバグが出る可能性が存在する

そのため、alpineは素のイメージサイズは小さいのですが、Pythonの実行環境としては不安が残ります。

Docker公式のPythonイメージ

PythonをDockerで使う場合は基本的にこのイメージを使う人が多いのではないでしょうか。大まかにタグの種類は以下になります。

イメージ種類 最新の一つ前のタグ(2020/03時点) サイズ 備考
alpine 3.8.1-alpine3.11 109MB こちらにもAlpineのイメージが存在しますが、上記に記載したデメリットがあるためお勧めではありません。
buster 3.8.1-buster 933MB Debianの最新バージョンであるBusterをベースとしたイメージです。多くの基本的なパッケージがインストールされているため軽量ではありませんが、汎用的な用途の場合はこちらを使うといいでしょう。
buster-slim 3.8.1-slim-buster 193MB Debianのイメージの内の共通パッケージを省き、その分イメージサイズが小さくなったバージョンです。Pythonアプリケーションのみを使用するのであれば問題はなさそうです。

タグを比較してみると、メインのDebianイメージに加えてslimバージョンも存在していることが分かります。こちらはPythonの実行に特化したイメージで、それ以外の多くの機能をバッサリ落としているようです。python-dockerページの注意書きに、「Python実行用途のみでイメージサイズに厳しい制限がある場合にslimを使用し、それ以外ではデフォルトのイメージをお勧めします」と記載してありました。コンテナ内でどこまで実行するかによって選択が変わってきますね。

結論

上記の検討結果から、Pythonアプリケーションをシンプルに動作させるのに適したDockerImageはDebian Buster slimという結論に至りました。イメージサイズは198MBで中々コンパクトですね。

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

AWS ECSのビルド〜デプロイをCodePipelineで自動化した

Docker + ECS + RailsのプロジェクトでCodePipelineを使用してデプロイまでを自動化したので、その知見をまとめたい。(ブルーグリーンデプロイではなく、通常のデプロイ時の方法)

RailsのプロジェクトだけれどCodePipeline基本的な使い方は、他の言語でもそれほど変わらないと思う

デプロイの流れ

githubにpushすれば自動でデプロイが開始される。デプロイは以下の流れで行うように作った。

  1. GitHubの特定のブランチ(masterなど)にpushする
  2. pushされたことがCodepipelineに通知されビルドが開始
    1. docker-composeを利用して、Dockerをbuildする
    2. Dockerイメージタグにコミット番号を付与して一意にする
    3. ビルドが完了したらECRにpushする
  3. ビルド完了後にECSにデプロイ通知がいく

CodePipelineの設定

CodePipelineはソース管理、ビルド、デプロイをパーツのようにつなげてCD/CIを管理することができるAWSのサービス。以下のサービスをつなぎ合わせて連携することができる

  • CodeCommit
  • CodeBuild
  • CodeDeploy

スクリーンショット_2020-03-09_14_55_31-2.png

CodeCommit

スクリーンショット_2020-03-09_15_24_30.png

まずはGithubで特定のリポジトリにpushされたときに検知できるようにする。ここではmasterがpushsされたときにビルドされる設定した。

ちなみにCodeCommitはGithub以外にも、ECRやS3などと連携することもできる。CodeCommit自体にコードを管理させることも可能。

CodeBuild

スクリーンショット_2020-03-09_15_29_41.png

CodeBuildではビルドプロジェクトというものを作成する。このビルドプロジェクトはOS環境や、ビルドコマンドを記載するbuildspec.ymlのパスを設定していく。ようはビルドの設定を管理している感じだ。

スクリーンショット 2020-03-09 15.38.01.png

Ubuntuでイメージが最新バージョンのものを使っておけば特に問題はないかと思う。buildspec.ymlはGithubにあげたプロジェクトに入れておく。そのパスをビルドプロジェクトで設定すればOK

buildspec.yml

ビルドするコマンドをyamlに書いていく。ビルドは以下のような流れで行う。

  1. ECRからDockerのイメージを取得
  2. コミットハッシュを取得する(コミットハッシュはDockerイメージタグとして使用する)
  3. .envに環境変数を追加していく
  4. docker-composeを利用してビルドする
  5. dbのmigrateを行う
  6. デプロイを通知する
version: 0.2

phases:
  install:
    runtime-versions:
      docker: 18
  pre_build:
    commands:
      - echo -------- Logging in to Amazon ECR... --------
      - aws --version
      - $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)
      - REPOSITORY_URI=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/hogehoge
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - IMAGE_TAG=${COMMIT_HASH:=latest}
  build:
    commands:
      - echo -------- Build started on `date` --------
      - echo -------- Building the Docker image... --------
      - echo AWS_ACCESS_KEY=$AWS_ACCESS_KEY >> .env
      - echo AWS_SECRET_KEY=$AWS_SECRET_KEY >> .env
      - echo ECS_ENV_NAME=$ECS_ENV_NAME >> .env
      - docker-compose -f docker-compose-$ECS_ENV_NAME.yml build
      - docker-compose -f docker-compose-$ECS_ENV_NAME.yml run --name hogehoge-image web sh -c "bundle exec rake db:create && bundle exec rake db:migrate"
      - docker commit hogehoge-image $REPOSITORY_URI:latest
      - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
  post_build:
    commands:
      - echo -------- Build completed on `date` --------
      - echo -------- Pushing the Docker images... --------
      - docker push $REPOSITORY_URI:latest
      - docker push $REPOSITORY_URI:$IMAGE_TAG
      - echo [\{\"name\":\"hogehoge\",\"imageUri\":\"$REPOSITORY_URI:$IMAGE_TAG\"\}] > imagedefinitions.json
artifacts:
  files: imagedefinitions.json

buildspec.ymlはpre_build、build、post_buildという3段階で処理を行う。一個ずつ分解して説明していく。

pre_build

ビルドする前の下準備。 GithubのURLとか、コミットハッシュとかをあとで使うので変数に入れている。ちなみにコミットハッシュはDockerイメージタグとして後で使う。

build
  - echo AWS_ACCESS_KEY=$AWS_ACCESS_KEY >> .env
  - echo AWS_SECRET_KEY=$AWS_SECRET_KEY >> .env
  - echo S3_BUCKET=$S3_BUCKET >> .env

環境変数を.envに書き込むようにしている。僕のRailsプロジェクトでは.envで環境変数を管理しており、CodeBuildでもdocker-composeが使用したいという理由でこの形にしている。このやり方がベストプラクティスではないような気がするので、もっと良い方法を見つけたい。

ちなみに環境変数はSystem Managerで管理している。環境変数についてはのちほどもう少し詳しく記載する。

  - docker-compose -f docker-compose-$ECS_ENV_NAME.yml build
  - docker-compose -f docker-compose-$ECS_ENV_NAME.yml run --name hogehoge-image web sh -c "bundle exec rake db:create && bundle exec rake db:migrate"
  - docker commit hogehoge-image $REPOSITORY_URI:latest
  - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:

あとはdocker-composeでビルドして、migrateして、Dockerイメージをcommitしているだけ。

ちなみに僕はDockerfile内にassets:precompileを行っているため、buildspec.ymlにはコマンドが書いていない。

post_build

ECSにデプロイするためには最終的にimagedefinitions.jsonというファイルを作成する必要がある。

  - docker push $REPOSITORY_URI:latest
  - docker push $REPOSITORY_URI:$IMAGE_TAG
  - echo [\{\"name\":\"hogehoge\",\"imageUri\":\"$REPOSITORY_URI:$IMAGE_TAG\"\}] > imagedefinitions.json
artifacts:
  files: imagedefinitions.json

imagedefinitions.jsonはnameとimageUirを関連付けたjsonを書いていく。複数環境あるときは当たり前だけど複数書いていく。

  • name
    • ECSのコンテナ名
  • imageUri
    • ECRのURL

ECSのタスク定義との関連は以下のようになる。

スクリーンショット_2020-03-09_16_34_33-2.png

補足

環境変数について

環境変数は秘匿化する必要があるためSystem Managerでパラメータを管理するようにした。

68017af5-fa1b-4fb2-8307-aadc174d004a-960x690r.png

安全な文字列を選択してパラメータを設定する。「名前」欄で設定した値をCodeBuildで使用する。

d9424b69-0d44-4230-afa8-c769f4a2e55a-960x573r-2.png

System Managerで設定した値をbuildspec.ymlで使用するために、CodeBuildに環境変数として設定する必要がある。これを設定しておくとbuildspec.ymlの中で$HOGEHOGEという値で使用できるようになる。

  • 名前
    • buildspec.ymlで使用する環境変数名
    • System Managerで設定した名前
  • 入力
    • 『パラメータ』を選択する
- echo AWS_ACCOUNT_ID=$AWS_ACCOUNT_ID >> .env
- echo AWS_ACCESS_KEY=$AWS_ACCESS_KEY >> .env
- echo AWS_SECRET_KEY=$AWS_SECRET_KEY >> .env

これでbuildspec.ymlにファイルの中でCodeBuildで設定した環境変数が利用できるようになる

デプロイしたときにタスク定義のバージョンは更新されていく

0ad3ec1b-5711-4351-a838-654d76ef1f81-960x596r.png

デプロイされるとimagedefinitions.jsonで設定したコンテナ名とイメージのURLでタスク定義のイメージが変更され、リビジョンが新しく更新されていく。タスクをリビジョンで管理するメリットとして「切り戻しが簡単になる」という点がある。

もしも本番で障害が発生したとき場合にもリビジョンを戻すだけで動作する。ただしDBのカラム変更などしているときは、DBをロールバックする必要もあるので注意が必要。

CodePipelineからデプロイを実行する

なんらかの理由でソースコードをpushせずにデプロイしたい場合は、Codepipelineの画面から直接行うことができる

c7177b94-b6e1-408f-84c4-de5ea8b817ad-960x598r.png

終わり

ECSは少人数の開発にこそ向いていると思う。ECSでスケールアップから障害復旧までまかせ、CodePipelineでデプロイを自動化しておけばインフラの運用をそれほど考慮しなくて済むようになる。アプリケーション層に集中して開発ができるようになる。

まだまだECSやCodePipelineに対しての知見が足りないので、また気づきがあったら書いていきたい

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

Rails + Docker + ECSのビルド〜デプロイをCodePipelineで自動化した

Docker + ECS + RailsのプロジェクトでCodePipelineを使用してデプロイまでを自動化したので、その知見をまとめたい。(ブルーグリーンデプロイではなく、通常のデプロイ時の方法)

RailsのプロジェクトだけれどCodePipeline基本的な使い方は、他の言語でもそれほど変わらないと思う

デプロイの流れ

githubにpushすれば自動でデプロイが開始される。デプロイは以下の流れで行うように作った。

  1. GitHubの特定のブランチ(masterなど)にpushする
  2. pushされたことがCodepipelineに通知されビルドが開始
    1. docker-composeを利用して、Dockerをbuildする
    2. Dockerイメージタグにコミット番号を付与して一意にする
    3. ビルドが完了したらECRにpushする
  3. ビルド完了後にECSにデプロイ通知がいく

CodePipelineの設定

CodePipelineはソース管理、ビルド、デプロイをパーツのようにつなげてCD/CIを管理することができるAWSのサービス。以下のサービスをつなぎ合わせて連携することができる

  • CodeCommit
  • CodeBuild
  • CodeDeploy

スクリーンショット_2020-03-09_14_55_31-2.png

CodeCommit

スクリーンショット_2020-03-09_15_24_30.png

まずはGithubで特定のリポジトリにpushされたときに検知できるようにする。ここではmasterがpushsされたときにビルドされる設定した。

ちなみにCodeCommitはGithub以外にも、ECRやS3などと連携することもできる。CodeCommit自体にコードを管理させることも可能。

CodeBuild

スクリーンショット_2020-03-09_15_29_41.png

CodeBuildではビルドプロジェクトというものを作成する。このビルドプロジェクトはOS環境や、ビルドコマンドを記載するbuildspec.ymlのパスを設定していく。ようはビルドの設定を管理している感じだ。

スクリーンショット 2020-03-09 15.38.01.png

Ubuntuでイメージが最新バージョンのものを使っておけば特に問題はないかと思う。buildspec.ymlはGithubにあげたプロジェクトに入れておく。そのパスをビルドプロジェクトで設定すればOK

buildspec.yml

ビルドするコマンドをyamlに書いていく。ビルドは以下のような流れで行う。

  1. ECRからDockerのイメージを取得
  2. コミットハッシュを取得する(コミットハッシュはDockerイメージタグとして使用する)
  3. .envに環境変数を追加していく
  4. docker-composeを利用してビルドする
  5. dbのmigrateを行う
  6. デプロイを通知する
version: 0.2

phases:
  install:
    runtime-versions:
      docker: 18
  pre_build:
    commands:
      - echo -------- Logging in to Amazon ECR... --------
      - aws --version
      - $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)
      - REPOSITORY_URI=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/hogehoge
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - IMAGE_TAG=${COMMIT_HASH:=latest}
  build:
    commands:
      - echo -------- Build started on `date` --------
      - echo -------- Building the Docker image... --------
      - echo AWS_ACCESS_KEY=$AWS_ACCESS_KEY >> .env
      - echo AWS_SECRET_KEY=$AWS_SECRET_KEY >> .env
      - echo ECS_ENV_NAME=$ECS_ENV_NAME >> .env
      - docker-compose -f docker-compose-$ECS_ENV_NAME.yml build
      - docker-compose -f docker-compose-$ECS_ENV_NAME.yml run --name hogehoge-image web sh -c "bundle exec rake db:create && bundle exec rake db:migrate"
      - docker commit hogehoge-image $REPOSITORY_URI:latest
      - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
  post_build:
    commands:
      - echo -------- Build completed on `date` --------
      - echo -------- Pushing the Docker images... --------
      - docker push $REPOSITORY_URI:latest
      - docker push $REPOSITORY_URI:$IMAGE_TAG
      - echo [\{\"name\":\"hogehoge\",\"imageUri\":\"$REPOSITORY_URI:$IMAGE_TAG\"\}] > imagedefinitions.json
artifacts:
  files: imagedefinitions.json

buildspec.ymlはpre_build、build、post_buildという3段階で処理を行う。一個ずつ分解して説明していく。

pre_build

ビルドする前の下準備。 GithubのURLとか、コミットハッシュとかをあとで使うので変数に入れている。ちなみにコミットハッシュはDockerイメージタグとして後で使う。

build
  - echo AWS_ACCESS_KEY=$AWS_ACCESS_KEY >> .env
  - echo AWS_SECRET_KEY=$AWS_SECRET_KEY >> .env
  - echo S3_BUCKET=$S3_BUCKET >> .env

環境変数を.envに書き込むようにしている。僕のRailsプロジェクトでは.envで環境変数を管理しており、CodeBuildでもdocker-composeが使用したいという理由でこの形にしている。このやり方がベストプラクティスではないような気がするので、もっと良い方法を見つけたい。

ちなみに環境変数はSystem Managerで管理している。環境変数についてはのちほどもう少し詳しく記載する。

  - docker-compose -f docker-compose-$ECS_ENV_NAME.yml build
  - docker-compose -f docker-compose-$ECS_ENV_NAME.yml run --name hogehoge-image web sh -c "bundle exec rake db:create && bundle exec rake db:migrate"
  - docker commit hogehoge-image $REPOSITORY_URI:latest
  - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:

あとはdocker-composeでビルドして、migrateして、Dockerイメージをcommitしているだけ。

ちなみに僕はDockerfile内にassets:precompileを行っているため、buildspec.ymlにはコマンドが書いていない。

post_build

ECSにデプロイするためには最終的にimagedefinitions.jsonというファイルを作成する必要がある。

  - docker push $REPOSITORY_URI:latest
  - docker push $REPOSITORY_URI:$IMAGE_TAG
  - echo [\{\"name\":\"hogehoge\",\"imageUri\":\"$REPOSITORY_URI:$IMAGE_TAG\"\}] > imagedefinitions.json
artifacts:
  files: imagedefinitions.json

imagedefinitions.jsonはnameとimageUirを関連付けたjsonを書いていく。複数環境あるときは当たり前だけど複数書いていく。

  • name
    • ECSのコンテナ名
  • imageUri
    • ECRのURL

ECSのタスク定義との関連は以下のようになる。

スクリーンショット_2020-03-09_16_34_33-2.png

補足

環境変数について

環境変数は秘匿化する必要があるためSystem Managerでパラメータを管理するようにした。

68017af5-fa1b-4fb2-8307-aadc174d004a-960x690r.png

安全な文字列を選択してパラメータを設定する。「名前」欄で設定した値をCodeBuildで使用する。

d9424b69-0d44-4230-afa8-c769f4a2e55a-960x573r-2.png

System Managerで設定した値をbuildspec.ymlで使用するために、CodeBuildに環境変数として設定する必要がある。これを設定しておくとbuildspec.ymlの中で$HOGEHOGEという値で使用できるようになる。

  • 名前
    • buildspec.ymlで使用する環境変数名
    • System Managerで設定した名前
  • 入力
    • 『パラメータ』を選択する
- echo AWS_ACCOUNT_ID=$AWS_ACCOUNT_ID >> .env
- echo AWS_ACCESS_KEY=$AWS_ACCESS_KEY >> .env
- echo AWS_SECRET_KEY=$AWS_SECRET_KEY >> .env

これでbuildspec.ymlにファイルの中でCodeBuildで設定した環境変数が利用できるようになる

デプロイしたときにタスク定義のバージョンは更新されていく

0ad3ec1b-5711-4351-a838-654d76ef1f81-960x596r.png

デプロイされるとimagedefinitions.jsonで設定したコンテナ名とイメージのURLでタスク定義のイメージが変更され、リビジョンが新しく更新されていく。タスクをリビジョンで管理するメリットとして「切り戻しが簡単になる」という点がある。

もしも本番で障害が発生したとき場合にもリビジョンを戻すだけで動作する。ただしDBのカラム変更などしているときは、DBをロールバックする必要もあるので注意が必要。

CodePipelineからデプロイを実行する

なんらかの理由でソースコードをpushせずにデプロイしたい場合は、Codepipelineの画面から直接行うことができる

c7177b94-b6e1-408f-84c4-de5ea8b817ad-960x598r.png

終わり

ECSは少人数の開発にこそ向いていると思う。ECSでスケールアップから障害復旧までまかせ、CodePipelineでデプロイを自動化しておけばインフラの運用をそれほど考慮しなくて済むようになる。アプリケーション層に集中して開発ができるようになる。

まだまだECSやCodePipelineに対しての知見が足りないので、また気づきがあったら書いていきたい

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

DockerのインストールとWordPressの起動 - さくらのクラウド超入門・サーバ編(4)

概要

前回はサーバの安全性を高めるため TCP Wrapper で SSH のアクセス制限 をしました。次は、クラウドを使ってインフラ(IaaS)の操作を行う前段階(の続き)です。

実際のアプリケーション動作環境を想像しやすいように、開発環境から整えます。コンテナを使った開発・実行環境である Docker をセットアップし、WordPress (PHP+MySQL)のコンテナを Docker Compose で実行する手順と、サーバに対する電源操作手順を学びます。

解説

アプリケーション開発と操作を簡単にする Docker とは?

Docker 普及前後で、サーバを準備した後の環境構築方法が大きく変わりました。Apache(アパッチ) や PHP(ピーエイチピー) などウェブサーバや言語開発環境を整える前に、今は「Docker」(ドッカー)をセットアップする方法があります。Docker があれば、yum (ヤム)や apt (アプト)を使わなくても、1つのホスト上で複数のサーバ機能や開発言語の環境を同居できます。 Docker についての詳細は、 こちらのスライド資料をご覧ください

Docker は、アプリケーションを簡単迅速に開発・移動・実行するためのプラットフォーム(プログラム群)です。Docker Engine(エンジン)というプログラムを通して、アプリケーションをコンテナ(と呼ばれる、名前空間やリソースを分離する技術の組み合わせた、プロセス間の隔離という「特別な状態」)として操作できます。Docker は 2013 年に dotCloud, Inc.(当時のドットクラウド社、現在 はDocker, Inc.)によってオープンソースとして公開されており、誰でも自由に使えます。

image.png

Docker でコンテナを操作するには Docker Engine をセットアップする必要があります。セットアップ後は docker コマンドを使うだけで、コンテナ化したアプリケーションを操作できます。

Docker のインストール用スクリプトの利用

CentOS 7 での Docker インストールは、 [get.docker.com])(https://get.docker.com) で配布されているインストール用スクリプトの利用が便利です。覚えておくのは get.docker.com というホスト名のみ。これをブラウザで開くと、インストール用スクリプトが表示されていて、さらに中にインストール用のコマンドも書かれています。

image.png

Docker Engine の正確な手順は、 パッケージ等のセットアップ が必要です。本番向けではないテストや検証用途であれば、Docker Engine のリポジトリと常に最新安定版の Docker Engine をセットアップする方法が簡単です(逆に言いますと、インストールする時点で Docker のバージョンが変わってしまうため、本番環境での利用は推奨されていません)。

インストールするには、サーバにログイン後、以下のコマンドを実行します。

curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh

コマンド実行直後は 20 秒間の待機時間です。その段階で Ctrl+C をキー入力するとインストールを中断できます。進行後は、セットアップが完了するまで待ちます。

セットアップが終わったら、Docker Engine デーモン(dockerd)の起動と、サーバ起動時に自動起動するコマンドを実行します。

systemctl start docker
systemctl enable docker

それから、 docker version コマンドを実行し、Docker のバージョン情報に Engine: の項目が出るかどうかを確認します。もし Engine の項目が表示されず Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? が出る場合、Docker のセットアップが完了していない可能性があります。あるいは、 Docker Engine が停止している場合は、起動します。

以上で Docker のセットアップ作業は完了です。

Docker で Nginx を動かす

Nginx コンテナを起動して、ウェブサーバが表示できるかどうか確認しましょう。

次のコマンドは nginx:alpine という Nginx が Alpine Linux (アルパイン・リナックス)上で動作する小さな Docker イメージを使い、 --detach (デタッチ)モードというバックグラウンドでコンテナを実行し、かつ、 --publish 8080:80 でホスト側ポート 8080 をコンテナ内のポート 80 に割り当て(マッピング)し、 --name でコンテナ名を mynginx とします。

docker run --detach --publish 8080:80 --name mynginx nginx:alpine

それから docker ps コマンドを実行すると、 mynginx という名前でコンテナが実行中( StatusUp )になっているのがわかります。

この状態で、ウェブブラウザを使い、 http://<サーバのIPアドレス>:8080 を実行します。次のような「Welcome to nginx!」(nginxへようこそ!)と表示されれば正常です。
image.png

実行しおわった後の nginx コンテナは停止・削除します。

docker kill mynginx
docker rm mynginx

Docker Compose のインストール

WordPress (ワードプレス)のようなアプリケーションは、複数のプログラムが動作します。複数のコンテナを扱うには、Docker Engine を単体で使うのよりも、Docker Compose(コンポーズ)という名前のツールと一緒に使うのが便利です。コンテナに関する情報などを YAML (ヤムル)ファイルに記述しておきます。

Docker Compose をインストールした環境であれば、コマンド docker-compose up を実行するだけで、複数のプログラムを同時に制御できます。

image.png

Docker Compose のインストール方法は複数ありますが、ここでは yum を使ってパッケージをインストールします。

yum -y install docker-compose

インストール後は、次のようにして、バージョン情報が表示されるかどうかを確認しましょう。

# docker-compose  version
docker-compose version 1.18.0, build 8dd22a9
docker-py version: 2.6.1
CPython version: 3.6.8
OpenSSL version: OpenSSL 1.0.2k-fips  26 Jan 2017

Docker Compose で WordPress を動かす

Docker Compose でプログラムを動かすには、まず作業用のディレクトリを作成し、移動します。ここでは mywp をいう名前のディレクトリを作成して移動します。

mkdir mywp
cd mywp

それから、 vi docker-compose.yaml などを実行し、同じディレクトリ内に YAML ファイルを作成します。内容は以下の通りにします( 参考: Docker Hub の wordpress イメージのドキュメント(英語) )。

yaml
version: '3.1'

services:

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

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

volumes:
  wordpress:
  db:

ここまで準備すると、同じディレクトリ内に docker-compose.yaml が出来ています。その状態で、次のコマンドを実行し、 Docker イメージのダウンロード(pull) と、Docker Compose のサービスとして複数のコンテナを同時実行します。

docker-compose pull
docker-compose up -d

そして、 docker-compose ps コマンドを実行し、2つのコンテナ( mywp_wordpress_1mywp_db_1)の StateUp になっているのを確認します。

# docker-compose ps
      Name                    Command               State          Ports
-------------------------------------------------------------------------------
mywp_db_1          docker-entrypoint.sh mysqld      Up      3306/tcp, 33060/tcp
mywp_wordpress_1   docker-entrypoint.sh apach ...   Up      0.0.0.0:80->80/tcp

今度はウェブブラウザで http://<サーバのIPアドレス> を実行します。今回の docker-compose.yaml の設定では、WordPress の wordpress コンテナを、ホスト側のポート 80 に割り当て(マッピング)するようにしました。そのため、ポート番号の入力は不要です(ウェブブラウザは、デフォルトのポート 80 を自動的に表示します)。

image.png

この図のように、WordPress 初期画面が表示されたら、初期設定をしてみましょう。

  1. 言語一覧では「日本語」を探し、「続ける」をクリックします。
  2. 次の画面では必要情報を入力の上「WordPressをインストール」をクリックします。

image.png

さらに次の画面では「ログイン」をクリックします。

image.png

そして、ユーザ名とパスワードを入力して「ログイン」しましょう。WordPress の管理画面にログインできたら、サイト名を確認したり、投稿したり、操作してみましょう。

また、 http://<サーバのIPアドレス> を実行し、 WordPress のコンテンツが表示されるのも確認しましょう。

image.png

以上で Docker Compose を使った WordPress のセットアップが完了しました。

サーバ電源操作と WordPress の動作確認

あとは、サーバが再起動しても表示されるかどうか、仮想マシンの停止・再起動を行い確認します。

さくらのクラウドのコントロールパネルを、ウェブブラウザで開き、「サーバ」一覧で対象サーバの情報を確認します。それから「サーバの行」をクリックして、行頭にあるチェックボックスに印を入れ、「電源操作」→「シャットダウン」をクリックします。

image.png

確認画面が表示されますので、作業対象のサーバが違っていないのを確認し、「シャットダウン」をクリックします。

image.png

操作確認ダイアログでは「実行」をクリックします。

image.png

そうすると、シャットダウン(停止)命令がサーバに対して送られます。ステータスが「成功」になるまで待ってから、次の確認画面は「閉じる」をクリックします。

image.png

そのままお待ちいただくと、サーバの状態を表す「緑色」のステータス(起動)が、「赤色」(準備中)から「灰色」(停止)に変わります。もし変わらなければ、コントロールパネル上の再読込ボタンをクリックします。

停止後は、再び起動しましょう。再びサーバをクリックしてチェックを入れ、「電源操作」→「起動」をクリックします。

image.png

対象のサーバを確認し、「起動」ボタンをクリックします。

あとは、確認ダイアログでは「実行」をクリックし、次の画面ではステータスが「成功」になってから「閉じる」をクリックします。しばらく待ち、サーバが起動状態になれば SSH の接続も出来ますし、WordPress も自動的に起動します。

ブラウザから http://<サーバのIPアドレス> を開き、 WordPress のサイトが表示できるかどうか確認します。

振り返り

Docker でコンテナを実行するには、Docker Engineのセットアップが必須です。 get.docker.com のセットアップ用スクリプトを使って手軽にセットアップし、Nginx ウェブサーバをコンテナで起動しました。 また、複数のコンテナを簡単に扱うためには、Docker Compose が便利であり、WordPress アプリケーションを Docker Compose で実行しました。あわせて、サーバの電源操作の仕方も学びました。

次回以降は、ここで作成した環境を使って、サーバのコピー方法やディスクの操作方法、クローンの作成の仕方を学びます。

参考情報

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

DockerのインストールとWordPressの起動 - さくらのクラウド超入門・サーバ編(3)

概要

前回はサーバの安全性を高めるため TCP Wrapper で SSH のアクセス制限 をしました。次は、クラウドを使ってインフラ(IaaS)の操作を行う前段階(の続き)です。

実際のアプリケーション動作環境を想像しやすいように、開発環境から整えます。コンテナを使った開発・実行環境である Docker をセットアップし、WordPress (PHP+MySQL)のコンテナを Docker Compose で実行する手順と、サーバに対する電源操作手順を学びます。

解説

アプリケーション開発と操作を簡単にする Docker とは?

Docker 普及前後で、サーバを準備した後の環境構築方法が大きく変わりました。Apache(アパッチ) や PHP(ピーエイチピー) などウェブサーバや言語開発環境を整える前に、今は「Docker」(ドッカー)をセットアップする方法があります。Docker があれば、yum (ヤム)や apt (アプト)を使わなくても、1つのホスト上で複数のサーバ機能や開発言語の環境を同居できます。 Docker についての詳細は、 こちらのスライド資料をご覧ください

Docker は、アプリケーションを簡単迅速に開発・移動・実行するためのプラットフォーム(プログラム群)です。Docker Engine(エンジン)というプログラムを通して、アプリケーションをコンテナ(と呼ばれる、名前空間やリソースを分離する技術の組み合わせた、プロセス間の隔離という「特別な状態」)として操作できます。Docker は 2013 年に dotCloud, Inc.(当時のドットクラウド社、現在 はDocker, Inc.)によってオープンソースとして公開されており、誰でも自由に使えます。

image.png

Docker でコンテナを操作するには Docker Engine をセットアップする必要があります。セットアップ後は docker コマンドを使うだけで、コンテナ化したアプリケーションを操作できます。

Docker のインストール用スクリプトの利用

CentOS 7 での Docker インストールは、 [get.docker.com])(https://get.docker.com) で配布されているインストール用スクリプトの利用が便利です。覚えておくのは get.docker.com というホスト名のみ。これをブラウザで開くと、インストール用スクリプトが表示されていて、さらに中にインストール用のコマンドも書かれています。

image.png

Docker Engine の正確な手順は、 パッケージ等のセットアップ が必要です。本番向けではないテストや検証用途であれば、Docker Engine のリポジトリと常に最新安定版の Docker Engine をセットアップする方法が簡単です(逆に言いますと、インストールする時点で Docker のバージョンが変わってしまうため、本番環境での利用は推奨されていません)。

インストールするには、サーバにログイン後、以下のコマンドを実行します。

curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh

コマンド実行直後は 20 秒間の待機時間です。その段階で Ctrl+C をキー入力するとインストールを中断できます。進行後は、セットアップが完了するまで待ちます。

セットアップが終わったら、Docker Engine デーモン(dockerd)の起動と、サーバ起動時に自動起動するコマンドを実行します。

systemctl start docker
systemctl enable docker

それから、 docker version コマンドを実行し、Docker のバージョン情報に Engine: の項目が出るかどうかを確認します。もし Engine の項目が表示されず Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? が出る場合、Docker のセットアップが完了していない可能性があります。あるいは、 Docker Engine が停止している場合は、起動します。

以上で Docker のセットアップ作業は完了です。

Docker で Nginx を動かす

Nginx コンテナを起動して、ウェブサーバが表示できるかどうか確認しましょう。

次のコマンドは nginx:alpine という Nginx が Alpine Linux (アルパイン・リナックス)上で動作する小さな Docker イメージを使い、 --detach (デタッチ)モードというバックグラウンドでコンテナを実行し、かつ、 --publish 8080:80 でホスト側ポート 8080 をコンテナ内のポート 80 に割り当て(マッピング)し、 --name でコンテナ名を mynginx とします。

docker run --detach --publish 8080:80 --name mynginx nginx:alpine

それから docker ps コマンドを実行すると、 mynginx という名前でコンテナが実行中( StatusUp )になっているのがわかります。

この状態で、ウェブブラウザを使い、 http://<サーバのIPアドレス>:8080 を実行します。次のような「Welcome to nginx!」(nginxへようこそ!)と表示されれば正常です。
image.png

実行しおわった後の nginx コンテナは停止・削除します。

docker kill mynginx
docker rm mynginx

Docker Compose のインストール

WordPress (ワードプレス)のようなアプリケーションは、複数のプログラムが動作します。複数のコンテナを扱うには、Docker Engine を単体で使うのよりも、Docker Compose(コンポーズ)という名前のツールと一緒に使うのが便利です。コンテナに関する情報などを YAML (ヤムル)ファイルに記述しておきます。

Docker Compose をインストールした環境であれば、コマンド docker-compose up を実行するだけで、複数のプログラムを同時に制御できます。

image.png

Docker Compose のインストール方法は複数ありますが、ここでは yum を使ってパッケージをインストールします。

yum -y install docker-compose

インストール後は、次のようにして、バージョン情報が表示されるかどうかを確認しましょう。

# docker-compose  version
docker-compose version 1.18.0, build 8dd22a9
docker-py version: 2.6.1
CPython version: 3.6.8
OpenSSL version: OpenSSL 1.0.2k-fips  26 Jan 2017

Docker Compose で WordPress を動かす

Docker Compose でプログラムを動かすには、まず作業用のディレクトリを作成し、移動します。ここでは mywp をいう名前のディレクトリを作成して移動します。

mkdir mywp
cd mywp

それから、 vi docker-compose.yaml などを実行し、同じディレクトリ内に YAML ファイルを作成します。内容は以下の通りにします( 参考: Docker Hub の wordpress イメージのドキュメント(英語) )。

yaml
version: '3.1'

services:

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

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

volumes:
  wordpress:
  db:

ここまで準備すると、同じディレクトリ内に docker-compose.yaml が出来ています。その状態で、次のコマンドを実行し、 Docker イメージのダウンロード(pull) と、Docker Compose のサービスとして複数のコンテナを同時実行します。

docker-compose pull
docker-compose up -d

そして、 docker-compose ps コマンドを実行し、2つのコンテナ( mywp_wordpress_1mywp_db_1)の StateUp になっているのを確認します。

# docker-compose ps
      Name                    Command               State          Ports
-------------------------------------------------------------------------------
mywp_db_1          docker-entrypoint.sh mysqld      Up      3306/tcp, 33060/tcp
mywp_wordpress_1   docker-entrypoint.sh apach ...   Up      0.0.0.0:80->80/tcp

今度はウェブブラウザで http://<サーバのIPアドレス> を実行します。今回の docker-compose.yaml の設定では、WordPress の wordpress コンテナを、ホスト側のポート 80 に割り当て(マッピング)するようにしました。そのため、ポート番号の入力は不要です(ウェブブラウザは、デフォルトのポート 80 を自動的に表示します)。

image.png

この図のように、WordPress 初期画面が表示されたら、初期設定をしてみましょう。

  1. 言語一覧では「日本語」を探し、「続ける」をクリックします。
  2. 次の画面では必要情報を入力の上「WordPressをインストール」をクリックします。

image.png

さらに次の画面では「ログイン」をクリックします。

image.png

そして、ユーザ名とパスワードを入力して「ログイン」しましょう。WordPress の管理画面にログインできたら、サイト名を確認したり、投稿したり、操作してみましょう。

また、 http://<サーバのIPアドレス> を実行し、 WordPress のコンテンツが表示されるのも確認しましょう。

image.png

以上で Docker Compose を使った WordPress のセットアップが完了しました。

サーバ電源操作と WordPress の動作確認

あとは、サーバが再起動しても表示されるかどうか、仮想マシンの停止・再起動を行い確認します。

さくらのクラウドのコントロールパネルを、ウェブブラウザで開き、「サーバ」一覧で対象サーバの情報を確認します。それから「サーバの行」をクリックして、行頭にあるチェックボックスに印を入れ、「電源操作」→「シャットダウン」をクリックします。

image.png

確認画面が表示されますので、作業対象のサーバが違っていないのを確認し、「シャットダウン」をクリックします。

image.png

操作確認ダイアログでは「実行」をクリックします。

image.png

そうすると、シャットダウン(停止)命令がサーバに対して送られます。ステータスが「成功」になるまで待ってから、次の確認画面は「閉じる」をクリックします。

image.png

そのままお待ちいただくと、サーバの状態を表す「緑色」のステータス(起動)が、「赤色」(準備中)から「灰色」(停止)に変わります。もし変わらなければ、コントロールパネル上の再読込ボタンをクリックします。

停止後は、再び起動しましょう。再びサーバをクリックしてチェックを入れ、「電源操作」→「起動」をクリックします。

image.png

対象のサーバを確認し、「起動」ボタンをクリックします。

あとは、確認ダイアログでは「実行」をクリックし、次の画面ではステータスが「成功」になってから「閉じる」をクリックします。しばらく待ち、サーバが起動状態になれば SSH の接続も出来ますし、WordPress も自動的に起動します。

ブラウザから http://<サーバのIPアドレス> を開き、 WordPress のサイトが表示できるかどうか確認します。

振り返り

Docker でコンテナを実行するには、Docker Engineのセットアップが必須です。 get.docker.com のセットアップ用スクリプトを使って手軽にセットアップし、Nginx ウェブサーバをコンテナで起動しました。 また、複数のコンテナを簡単に扱うためには、Docker Compose が便利であり、WordPress アプリケーションを Docker Compose で実行しました。あわせて、サーバの電源操作の仕方も学びました。

次回以降は、ここで作成した環境を使って、サーバのコピー方法やディスクの操作方法、クローンの作成の仕方を学びます。

参考情報

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

DockerイメージにLinuxbrewをDockerfileでインストールする

この記事は株式会社クロノス「~2020年春~勝手にやりますアドベントカレンダー」の7日目の記事です!

はじめに

自分のパソコンでは環境が出来上がっている状態で
みんなで環境構築をやろう! 教えよう! となったとき。
みなさんならどうするでしょうか。

細かい差異も見逃さないために、自分のパソコンに入っているパッケージをアンインストールして、環境構築の実演をしてみせるでしょうか。

(自分のパソコンの環境をいじるのは面倒くさいので)細かいことは気にせず、手順書を作って見守る(適宜困ったことがあったら助ける)でしょうか。

今回は、「自分の環境を汚したくないけど、確実に環境構築をやってもらうために実演をしたい」ってときにDockerコンテナ使えないかなーと色々調べてみた結果は紹介させてもらいます。
※ あまり実践的ではないです。

知れること・知れないこと

※ 結果的にやっていることはubuntu18.04のイメージにLinuxbrewをDockerfileでインストールする方法になります。

知れること

  • ubuntuのイメージにLinuxbrewをDockerfileを使ってインストールする方法
  • 素人目線のDockerfileの書き方のポイント

知れないこと

  • windowsで環境構築を実演する際のDockerイメージの作り方

実現方法の考え方

おおよそmacではHomebrew使って、パッケージインストールして、環境構築等するだろうという偏見仮定の元、
「LinuxbrewをLinuxのDockerイメージに入れたら、見かけ上はmacでHomebrew使うのと変わらなくない? ほらコマンドもbrewで変わらないし……」
という安直な考えで、Linuxbrewの利用を考えます。

Linuxbrew

LinuxbrewとはHomebrewのLinux版(名前そのまま)です。
2019年2月から本家 Homebrew 2.0.0 にてLinuxbrewが正式にサポートされるとのことなので(homebrew-2.0.0)、使わない手はないです。

参考
LinuxbrewでUbuntu18.04のパッケージ管理
Linuxbrewのススメ

目標

作成したイメージのDockerコンテナに入って

$ brew doctor

とコマンドを叩いて、一発でYour system is ready to brew.と出てくることを目指します。

ホームディレクトリにLinuxbrewをインストールするDockerfile

こちらは道半ばバージョンです。
Linuxbrewは/home/linuxbrew/.linuxbrew下にインストールすることが推奨されています。
上記デフォルトのディレクトリにインストールすることで、ビルド済みバイナリがあるパッケージはバイナリをインストールできるようになります。
そのためインストール場所がデフォルトでないとbrew doctorでWarningが出ます。

ただ、Dockerfileで/home/linuxbrew/.linuxbrew配下にLinuxbrewをインストールするには、私が試した限り、少々特殊なことをする必要がありました。
こちらはこちらで「素直にDockerfileを書いたバージョン」として、記載しておきたいと思います。

こちらの方法だと/home/[ユーザ名]/.linuxbrew/にインストールされることになります。Warningが出るだけでLinuxbrewは普通に利用可能です。

FROM ubuntu:18.04
LABEL maintainer beeeeyan
#環境変数を設定
ENV DEBIAN_FRONTEND=noninteractive
ENV USER beeeeyan
ENV HOME /home/${USER}
ENV SHELL /bin/bash
ENV PW password

# 種々インストール
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
    vim \
    sudo \
    locales \
    build-essential \
    ca-certificates \
    curl \
    file \
    git && \
    # 一般ユーザーアカウント追加
    useradd -m ${USER} && \
    # 一般ユーザーにsudo権限を付与
    gpasswd -a ${USER} sudo && \
    # 一般ユーザーのパスワードを設定
    echo "${USER}:${PW}" | chpasswd && \
    # ログインシェルを指定
    sed -i.bak -r s#${HOME}:\(.+\)#${HOME}:${SHELL}# /etc/passwd && \
    # localの設定
    locale-gen en_US.UTF-8

# コマンドを実行するUSERを変更
USER ${USER}
# 作業ディレクトリを指定
WORKDIR ${HOME}

# Linuxbrewをインストール
RUN sh -c "$(curl -fsSL https://raw.githubusercontent.com/Linuxbrew/install/master/install.sh)" && \
# pathの設定
    echo 'export PATH=${HOME}/.linuxbrew/bin:$PATH' >> .bash_profile

説明

素人目線のDockerfileの書き方にも触れています。
今回の「Linuxbrewのインストール」に深く関わる箇所には気持ち最初に☆付けました。

LABEL maintainer beeeeyan

MAINTAINER(作成者情報)はDocker 1.13から非推奨(deprecated)となっているそうです。LABELで書きましょう。
参考 : 1.13以降はMAINTAINERの代わりにLABELを使うようにしよう

ENV DEBIAN_FRONTEND=noninteractive

brew doctorで出てくるエラーを潰す仮定でlocalesをインストールすることにしたのですが(後述)、localesをインストールしようとするとapt-get install自体が止まったので設定しました。詳しくは以下参照ください。
参考 : Docker Ubuntu18.04でtzdataをinstallするときにtimezoneの選択をしないでinstallする

RUN apt-get update && \
    apt-get install -y --no-install-recommends \

ここいらへんは呪文ですね。--no-install-recommendsの意味を知らなかったのですが「指定したもの以外余計なものは入れない」ということみたいです。
参考 : #Linux #Ubuntu #docker #Dockerfile のこれは何? apt-get install --no-install-recommends

    vim \
    sudo \
    locales \
    build-essential \
    ca-certificates \
    curl \
    file \
    git && \
    ~省略~
   # localの設定
    locale-gen en_US.UTF-8
  • vimsudo
    あると便利かな、くらいで入れています。sudoに関しては「完成版」では必須です。

  • locales…(省略)…locale-gen en_US.UTF-8
    brew doctorとコマンド叩いて、以下のようなエラーが出たのでインストール&設定しました。

    warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8): No such file or directory …(省略)

  • build-essentialca-certificatescurlfilegit
    調べてもらったらわかりますが、build-essentialcurlfilegitについては、Linuxbrewをインストールするときに公式で先にインストールするよう指定されているパッケージです。
    ポイントは ca-certificatesを指定するところにあります。(これがないとcurlなどを使っても通信ができません)このパッケージは普段は自動でインストールされるものです。勘のいい方はお分かりかと思いますがapt-get install--no-install-recommendsを指定した影響で明示的に書いておく必要が出てきたパッケージです。
    参考 : 公式

    # 一般ユーザーアカウント追加
    useradd -m ${USER} && \
    # 一般ユーザーにsudo権限を付与
    gpasswd -a ${USER} sudo && \
    # 一般ユーザーのパスワードを設定
    echo "${USER}:${PW}" | chpasswd && \
    # ログインシェルを指定
    sed -i.bak -r s#${HOME}:\(.+\)#${HOME}:${SHELL}# /etc/passwd && \

☆ 一般ユーザを追加している部分
ユーザを追加する理由はrootユーザでLinuxbrewをインストールしようとするエラーになるからです。参考先の情報からsedコマンドの書き方は多少調整しました。
参考 : Dockerで開発環境を仮想化する

# コマンドを実行するUSERを変更
USER ${USER}
# 作業ディレクトリを指定
WORKDIR ${HOME}

コマンドを実行するユーザとディレクトリを作成したユーザに変更しています。

後は公式のインストールコマンド叩いて.bash_profileにLinuxbrewのパス(今回だと/home/[ユーザ名]/.linuxbrew/bin)を指定するだけです。

なぜ「素直な書き方」では/home/linuxbrew/.linuxbrewにインストールできないのか?

素直な書き方だとビルド中に以下のような応答が求められます。

==> Select the Homebrew installation directory
- Enter your password to install to /home/linuxbrew/.linuxbrew (recommended)
- Press Control-D to install to /home/[ユーザ名]/.linuxbrew
- Press Control-C to cancel installation

ここでEnter your passwordできなくてつみます。
ただ不思議とControl-Dの挙動はしてくれて、/home/[ユーザ名]/.linuxbrewにインストールされるという寸法です。

[完成版]/home/linuxbrew/.linuxbrewにLinuxbrewをインストールするDockerfile

ほんの少しトリッキーなことをしたのですが、以下でうまくいきました。

FROM ubuntu:18.04
LABEL maintainer beeeeyan
#環境変数を設定
ENV DEBIAN_FRONTEND=noninteractive
ENV USER beeeeyan
ENV HOME /home/${USER}
ENV SHELL /bin/bash
ENV PW password

# 種々インストール
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
    vim \
    sudo \
    locales \
    build-essential \
    ca-certificates \
    curl \
    file \
    git && \
    # 一般ユーザーアカウント追加
    useradd -m ${USER} && \
    # 一般ユーザーにsudo権限を付与
    gpasswd -a ${USER} sudo && \
    # 一般ユーザーのパスワードを設定
    echo "${USER}:${PW}" | chpasswd && \
    # ログインシェルを指定
    sed -i.bak -r s#${HOME}:\(.+\)#${HOME}:${SHELL}# /etc/passwd && \
    # localの設定
    locale-gen en_US.UTF-8 && \
    # linuxbrewの「Alternative Installation」を実行
    git clone https://github.com/Homebrew/brew /home/linuxbrew/.linuxbrew/Homebrew && \
    mkdir /home/linuxbrew/.linuxbrew/bin && \
    ln -s /home/linuxbrew/.linuxbrew/Homebrew/bin/brew /home/linuxbrew/.linuxbrew/bin && \
    eval $(/home/linuxbrew/.linuxbrew/bin/brew shellenv)

# コマンドを実行するUSERを変更
USER ${USER}
# 作業ディレクトリを指定
WORKDIR ${HOME}

# Linuxbrew関連のフォルダ作成
RUN echo ${PW} | sudo -S mkdir -p /home/linuxbrew/.linuxbrew/etc \
    /home/linuxbrew/.linuxbrew/include \
    /home/linuxbrew/.linuxbrew/lib \
    /home/linuxbrew/.linuxbrew/opt \
    /home/linuxbrew/.linuxbrew/sbin \
    /home/linuxbrew/.linuxbrew/share \
    /home/linuxbrew/.linuxbrew/var/homebrew/linked \
    /home/linuxbrew/.linuxbrew/var/homebrew/locks \
    /home/linuxbrew/.linuxbrew/Cellar && \
    # 権限変更
    echo ${PW} | sudo -S chown -R ${USER} /home/linuxbrew/.linuxbrew/etc \
    /home/linuxbrew/.linuxbrew/include \
    /home/linuxbrew/.linuxbrew/lib \
    /home/linuxbrew/.linuxbrew/opt \
    /home/linuxbrew/.linuxbrew/sbin \
    /home/linuxbrew/.linuxbrew/share \
    /home/linuxbrew/.linuxbrew/var/homebrew/linked \
    /home/linuxbrew/.linuxbrew/Cellar \
    /home/linuxbrew/.linuxbrew/Homebrew \
    /home/linuxbrew/.linuxbrew/bin \
    /home/linuxbrew/.linuxbrew/var/homebrew/locks && \
    # パスの設定
    echo 'export PATH="/home/linuxbrew/.linuxbrew/bin:$PATH"' >> .bash_profile && \
    # パスの反映
    . ~/.bash_profile && \
    # brew doctorの実行
    brew doctor

ポイントは公式の「Alternative Installation」の部分です。

    # linuxbrewの「Alternative Installation」を実行
    git clone https://github.com/Homebrew/brew /home/linuxbrew/.linuxbrew/Homebrew && \
    mkdir /home/linuxbrew/.linuxbrew/bin && \
    ln -s /home/linuxbrew/.linuxbrew/Homebrew/bin/brew /home/linuxbrew/.linuxbrew/bin && \
    eval $(/home/linuxbrew/.linuxbrew/bin/brew shellenv)

「Alternative Installation」で説明されている内容は、任意の場所にLinuxbrewをインストールする際によく利用されるコマンドです。これをデフォルトの場所にインストールする方式で利用します。
つまり「Alternative Installation」に書かれているインストール場所~/.linuxbrew/Homebrew/home/linuxbrew/.linuxbrew/Homebrewに変更します。(変更した状態のものが上記コマンドです)

# Linuxbrew関連のフォルダ作成
RUN echo ${PW} | sudo -S mkdir -p /home/linuxbrew/.linuxbrew/etc \
    /home/linuxbrew/.linuxbrew/include \
    /home/linuxbrew/.linuxbrew/lib \
    /home/linuxbrew/.linuxbrew/opt \
    /home/linuxbrew/.linuxbrew/sbin \
    /home/linuxbrew/.linuxbrew/share \
    /home/linuxbrew/.linuxbrew/var/homebrew/linked \
    /home/linuxbrew/.linuxbrew/var/homebrew/locks \
    /home/linuxbrew/.linuxbrew/Cellar && \
    # 権限変更
    echo ${PW} | sudo -S chown -R ${USER} /home/linuxbrew/.linuxbrew/etc \
    /home/linuxbrew/.linuxbrew/include \
    /home/linuxbrew/.linuxbrew/lib \
    /home/linuxbrew/.linuxbrew/opt \
    /home/linuxbrew/.linuxbrew/sbin \
    /home/linuxbrew/.linuxbrew/share \
    /home/linuxbrew/.linuxbrew/var/homebrew/linked \
    /home/linuxbrew/.linuxbrew/Cellar \
    /home/linuxbrew/.linuxbrew/Homebrew \
    /home/linuxbrew/.linuxbrew/bin \
    /home/linuxbrew/.linuxbrew/var/homebrew/locks && \

後半はbrew doctorと打って怒られる部分を先回りして処理しています。
sudoで求められるパスワードはecho-Sオプションで乗り越え、
前半は必要なフォルダの作成・後半はフォルダの権限の変更(rootユーザではLinuxbrewが利用できないので)、、といった具合です。
権限の変更はエラーでは${whoami}でしたが、Dockerfileの統一感を考えて${USER}でここでは書きました。

    # brew doctorの実行
    brew doctor

パスを追加して、最後に一回brew doctorを実行しておきます。
一回目のbrew doctorでは、一部brewのツールらしきものがインストールされます。(一発でYour system is ready to brewと出てこない!)

コンテナを立ち上げるまで〜〜

ビルドします。(試行錯誤する段階では、私はAutomated Build Dockerでやってました)
Dockerfileがカレントディレクトリに存在する状態で以下コマンドを実行

$ docker build -t [イメージ名の指定] .

コンテナの起動

$ docker run --name [コンテナ名の指定] -it -d [イメージ名] /bin/bash

起動中のコンテナに入る
※ちょっとした話ですが--loginをつけておかないと.bash_profileに指定した設定が反映されないのでご注意ください。(パスが反映されずbrew doctorが実行できない…)

起動中のコンテナに入る

$ docker exec -it [コンテナ名] /bin/bash --login

brew doctor
スクリーンショット 2020-03-06 0.22.19.png

今回の目標達成です!

参考

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

コーディング未経験のPO/PdMのためのRails on Dockerハンズオン vol.10 - TDD & Test Automation -

はじめに

記念すべき第10回目(ドドーン)。

前回まででサインアップ、サインインの機能を作っていきました。
ただどんどんアプリケーションを作っていくうちに、テスト、面倒になってきましたね。デグレも気になるし...

ということで今回は少しアプリ開発を離れまして、TDD(Test Driven Development)、そしてテスト自動化を体得していきましょう!

前回のソースコード

前回のソースコードはこちらに格納してます。今回のハンズオンからやりたい場合はこちらからダウンロードしてください。

TDD

まずはTDDをご紹介。
特にアジャイル開発をやったりすると耳にする単語ですよね。
Test Driven Development、日本語だとテスト駆動開発です。
なんちゃら駆動開発って色々あるんですが、まぁ『なんちゃら』部分を第1に考えた開発って感じで、TDDの場合はテストファーストで開発をしていくって意味ですね。

TDDでは『レッド』『グリーン』『リファクタリング』の3つのフェーズを辿ります。

レッド

『レッド』はテストが通らないフェーズです。
TDDではまず機能をコーディングする前に期待動作のテストコードを書きます。
当然、機能をコーディングしていないのですからテストは通りません。この状態が『レッド』です。
TDDでは、この『レッド』のフェーズから機能をコーディングしていって『グリーン』のフェーズをめざします。

グリーン

『グリーン』はテストがとりあえず通ったフェーズです。
『レッド』フェーズからアプリをコーディングしていってテストコードが全て通った状態が『グリーン』フェーズです。

リファクタリング

『リファクタリング』はテストが通っていてコードとしても良い状態になったフェーズです。
『グリーン』フェーズは、『とりあえず動く』という状態です。
この『グリーン』な状態を保ちつつ、コードをより可読性高く、効率的に変更していくフェーズが『リファクタリング』です。
TDDではテスト自動化を行いますが、これがあるからこそ『動く』状態をキープしながらよりよいコードをめざせます。

TDDでは、機能やユーザーストーリー単位にこれらの3つのフェーズで開発を進めていくことで、デグレなくよいコードを作り上げることができます。

BDD

TDDの派生として、BDD(Behavior Driven Development)があります。
日本語では『振る舞い駆動開発』と言われています。

これは、TDDよりもより要望に近いテストを記述するフォーマットのようなものと覚えてもらえればよいかと思います。
開発の進め方はTDDと同じで、テストコードを記述して『レッド』『グリーン』『リファクタリング』をしていきます。

BDDでは、GivenWhenThenの3つをテストケースとして定義します。

  • Given: 前提条件です。「サインイン済みのユーザーが」って感じです。
  • When: 操作や入力です。「プロフィールページでサインアウトリンクをクリックしたとき」って感じです。
  • Then: 期待結果です。「未サインイン状態になりトップページに遷移すること」って感じです。

はい、例を出してますが、BDDにのっとると例えば

サインイン済みのユーザーが、プロフィールページでサインアウトリンクをクリックしたとき、未サインイン状態になりトップページに遷移すること

というようなテスト項目を作れます。このようにBDDはよりユーザー目線の振る舞いをテスト項目として定義する考え方です。

このハンズオンでは、このBDDを使ってテストを定義し、テストコードを書いていきます。

テスト自動化

さて、アジャイル開発やTDD/BDDを採用する場合、テスト自動化が必須です。
というよりも一回作ったらもう絶対に追加で開発をしないシステム(どんなシステム?)以外は必須だと思っています。
リファクタリングをしていったり新しい機能を作っていくときに、全てのテストケースをいちいち人間の手でやっていくのは、時間的にも人員的にも不可能だとわかりますよね。

そこで、このハンズオンでもテスト自動化を導入していきます。

Rails(Ruby)には使いやすいテストフレームワークRSpecがあります。
またプログラムでWeb画面を操作するSeleniumとこれらをよりコーディングしやすくラッピングしてくれるCapybaraというフレームワークがあります。
このハンズオンでは、この3つのツールを使って実際にユーザーが操作しているのと同じ状態をテストするE2E(End to End)テストを自動化してみます。

必要なライブラリ類をインストールする

まずはテスト自動化を実行するために必要なライブラリ類をDockerイメージやRailsアプリにインストールしていきます。
最初に、Dockerコンテナ内でブラウザを立ち上げられないとE2Eテストが行えないので、Dockerfileを編集してDockerイメージにブラウザをインストールします。
今回はChromeを立ち上げてテストできるようにします。

Dockerfile
  FROM ruby:2.6.5-alpine3.11

  ENV HOME="/app"
  ENV LANG=C.UTF-8
  ENV TZ=Asia/Tokyo

  WORKDIR $HOME

  RUN apk update && \
      apk upgrade && \
      apk add --no-cache \
          gcc \
          g++ \
          less \
          libc-dev \
          libxml2-dev \
          linux-headers \
          make \
          nodejs \
          postgresql \
          postgresql-dev \
          tzdata \
          yarn && \
+     apk add --no-cache \
+         chromium \
+         chromium-chromedriver \
+         dbus \
+         mesa-dri-swrast \
+         ttf-freefont \
+         udev \
+         wait4ports \
+         xorg-server \
+         xvfb \
+         zlib-dev && \
    apk add --virtual build-packs --no-cache \
      build-base \
      curl-dev

COPY Gemfile $HOME
COPY Gemfile.lock $HOME

RUN bundle install && \
    apk del build-packs

COPY . $HOME
EXPOSE 3000
CMD ["rails", "server", "-b", "0.0.0.0"]

これらのパッケージを追加することでDockerコンテナ内でChromeブラウザを起動させてテストすることができるようになります。

次にRailsアプリに必要なライブラリを追加していきます。

Gemfile
  ...
  group :development, :test do
    gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
    gem 'pry-rails'
+
+   # For test automation
+   gem 'rspec-rails', '~>3.9'
+   gem 'capybara'
+   gem 'selenium-webdriver'
  end
  ...

ここまでで一度Dockerイメージを再ビルドしましょう。

$ docker-compose build

必要なライブラリ類がDockerイメージにインストールされた状態になります。

RSpecの初期設定をする

次にRailsアプリにRSpecの初期インストールをしていきます。
まず、ビルドしたDockerイメージからDockerコンテナを立ち上げて、rails g rspec:installコマンドを実行します。

$ docker-compose up -d
$ docker-compose exec web ash
# rails g rspec:install
Running via Spring preloader in process 131
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

色々とファイルが出来上がりました。それぞれの役割は以下の通りです。

  • .rspec: RSpecを実行するにあたりimportする設定ファイルを定義している
  • spec/spec_helper.rb: RSpecの設定ファイル
  • spec/rails_helper.rb: RSpecのRails要素をプラスした設定ファイル

rails_helper.rbspec_helper.rbをオーバーライドしている間柄ですね。

ここで.rspecを少し編集します。

.rspec
- --require spec_helper
+ --require rails_helper

これでデフォルトでrails_helper.rbが設定ファイルとして読み込まれるようになりました。
rails_helper.rbにRSpecで利用するWebドライバーの設定をしていきます。

spec/rails_helper.rb
  require 'spec_helper'
  ENV['RAILS_ENV'] ||= 'test'

  require File.expand_path('../config/environment', __dir__)

  abort("The Rails environment is running in production mode!") if Rails.env.production?
  require 'rspec/rails'

  # Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

  begin
    ActiveRecord::Migration.maintain_test_schema!
  rescue ActiveRecord::PendingMigrationError => e
    puts e.to_s.strip
    exit 1
  end
+
+ Capybara.register_driver :selenium_chrome_headless do |app|
+   options = ::Selenium::WebDriver::Chrome::Options.new
+   options.add_argument('--no-sandbox')
+   options.add_argument('--headless')
+   options.add_argument('--disable-gpu')
+   options.add_argument('--disable-dev-shm-usage')
+   options.add_argument('--window-size=1680,1050')
+   Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
+ end

  RSpec.configure do |config|
+   # Driver setting for system tests.
+   config.before(:each, type: :system) do
+     driven_by :selenium_chrome_headless
+   end
+ 
+   config.before(:each, type: :system, js: false) do
+     driven_by :rack_test
+   end

  config.fixture_path = "#{::Rails.root}/spec/fixtures"
  config.use_transactional_fixtures = true
  config.infer_spec_type_from_file_location!
  config.filter_rails_from_backtrace!
end

Capybara.register_driverでWebドライバーの設定を新しく作ります。
ちょっと書き方が独特な気がすると思いますが、こういうものなのだと思ってください。笑
途中に--headlessという設定がありますが、headlessは目に見える形でブラウザを立ち上げることなくブラウザテストを行うことができるものです。(ヘッドレス Chrome ことはじめ  |  Web  |  Google Developers
これによってテストの実行速度が速くなったりするので是非使いましょう。

RSpec.configureで2つのパターンのWebドライバー設定をしています。
config.before(:each, type: :system)はシステムテストが実行される都度設定されるものことを意味しています。(システムテストはRSpecの用語でE2Eテストと同義と思っていただいていいかと)
driven_byでドライバーを指定するのですが、デフォルトでは先ほど上で定義したselenium_chrome_headlessが、js: falseの場合にはrack_testが設定されることがわかりますね。
rack_testは高速にテストができるのですがjavascriptの機能などを見ることはできないドライバーです。
javascriptの確認をする必要がなく高速にテストを終えたいケースや、javascriptを動かしたくないテストケースに使えます。

また、このままではrails gコマンドを実行したときに意図しないテストファイルが自動生成されてしまいます。
面倒なのでテスト関連のファイルが生成できないようにします。

config/application.rb
  ...
  module App
    class Application < Rails::Application
      config.load_defaults 6.0

      # Don't generate system test files.
      config.generators.system_tests = nil

      # Timezone
      config.time_zone = 'Tokyo'
      config.active_record.default_timezone = :local

      # Language
      config.i18n.default_locale = :ja
+
+     # Don't create test files atomatically.
+     config.generators do |g|
+       g.test_framework :rspec,
+         fixtures: false,
+         view_specs: false,
+         helper_specs: false,
+         routing_specs: false,
+         controller_specs: false,
+         request_specs: false
+     end
    end
  end

ここまででRSpecでE2Eテストを実行する初期設定ができました!

テストが実行できるか試してみる

まずは試しにここまでの設定でちゃんとテスト自動化ができるのか試してみましょう。

まず、RSpecのシステムテストを実行するファイルを格納するspec/systemディレクトリを作成し、その中にsample_spec.rbの名前のサンプルファイルを用意してみましょう。

# mkdir spec/system
# touch spec/system/sample_spec.rb

RSpecは_spec.rbをテストコードと判断して実行するようになっているのでお忘れなきよう。

ではsample_spec.rbに「トップページにアクセスできること」を確認するテストコードを記述していきます。

spec/system/sample_spec.rb
feature "サンプルテスト", type: :system do
  scenario "トップページにアクセスできること" do
    visit root_path
    expect(current_path).to eq root_path
  end
end

テストコード自体はscenarioブロックに囲まれた部分です。featureは複数のscenarioを束ねるものでtype: :systemオプションをつけることでシステムテストとして実行することを宣言しています。

「トップページにアクセスできること」シナリオのテストコードの中身もちょっと紹介します。
visit root_pathroot_path、つまり/にアクセスしています。
expect([検査対象]).to [期待結果][検査対象][期待結果]であるかどうかを検査します。
今回の検査対象はcurrent_pathです。これは今表示されているページのパスのことです。期待結果がroot_pathなので、現在表示されているページが/であればOK、そうでなければNGになります。

実はここで使っているvisitcurrent_pathはCapybaraのおかげでわかりやすい言葉で使えるようになっています。Capybaraの使い方は「使えるRSpec入門・その4「どんなブラウザ操作も自由自在!逆引きCapybara大辞典」 - Qiita」などがわかりやすいと思います。あとは全力で公式ドキュメントよむ。

このように操作検査が一つのシナリオの中に記述され、検査が通るかどうかでOK/NGが判断されます。
もちろん、操作も検査も1つのシナリオの中で複数記述することができます。

では、このテストを実行してみましょう。

# rspec spec/system/sample_spec.rb
Capybara starting Puma...
* Version 4.3.1 , codename: Mysterious Traveller
* Min threads: 0, max threads: 4
* Listening on tcp://127.0.0.1:40933
.

Finished in 16.97 seconds (files took 16.66 seconds to load)
1 example, 0 failures

RSpecのテストを実行するときはrspecコマンドを使います。今回の例のようにファイル名を指定することで、そのテストファイルのみが実行されます。
ディレクトリを指定した場合は、そのディレクトリの全てのテストコードが、rspecだけの場合はspecディレクトリの全てのテストファイルが実行されるます。

最後に1 example, 0 failuresと表示されています。exampleはシナリオ数、failuresはそのうちNGだった数を表すので、今回は1つのテストシナリオが実行され全てOKであったことがわかります。

試しに、エラーになったときにどうなるか試してみましょう。

spec/system/sample_spec.rb
  feature "サンプルテスト", type: :system do
    scenario "トップページにアクセスできること" do
      visit root_path
-     expect(current_path).to eq root_path
+     expect(current_path).to eq sign_up_path
    end
  end
Capybara starting Puma...
* Version 4.3.1 , codename: Mysterious Traveller
* Min threads: 0, max threads: 4
* Listening on tcp://127.0.0.1:37237

F

Failures:

  1) サンプルテスト トップページにアクセスできること
     Failure/Error: expect(current_path).to eq sign_up_path

       expected: "/sign_up"
            got: "/"

       (compared using ==)



     # ./spec/system/sample_spec.rb:4:in `block (2 levels) in <main>'

Finished in 4.23 seconds (files took 6.85 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/system/sample_spec.rb:2 # サンプルテスト トップページにアクセスできること

1 example, 1 failureなのでNGになっていることがわかりますね。
途中の内容をみてみると、expected: "/sign_up"に対してgot: "/"であるためにNGになっていることが分かります。想定通りテストがNGになりました。

RSpecのシステムテストではテストがNGになった場合、自動的にスクリーンショットを保存して置いてくれます。そのファイルはtmp/screenshots/に保存されます。
ただ、ファイルをみると真っ白。本当はroot_pathにアクセスしているのでトップページが表示されていてほしいですよね。
実はこれRSpecのバグっぽいんですよね...(Rails アプリケーションの不安定なテストを撲滅したい 〜system spec のデバッグ方法とテストを不安定にさせる要因〜 - あらびき日記

ということでスクリーンショットがちゃんと表示されるようにヘルパーを作ってみましょう!

スクリーンショットを正しく表示させる

まずはテストを実行するときにヘルパーファイルを読み取るようにします。
これはrails_helper.rbでコメントアウトを外すだけでOKです。

spec/rails_helper.rb
  ...
- # Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }
+ Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }
  ...

これで、spec/support/**/*.rbのファイルがテスト実行時に読み込まれるようになります。
ヘルパーファイルを格納しておくディレクトリを作って、スクリーンショットを表示させるためのヘルパーとしてvisible_screenshot_helper.rbを作りましょう!

# mkdir -p spec/support/helpers
# touch spec/support/helpers/visible_screenshot_helper.rb
visible_screenshot_helper.rb
module VisibleScreenshotHelper extend ActiveSupport::Concern

  included do |example_group|
    example_group.after do
      take_failed_screenshot
    end
  end

  def take_failed_screenshot
    return if @is_failed_screenshot_taken
    super
    @is_failed_screenshot_taken = true
  end

end

RSpec.configure do |config|
  config.include VisibleScreenshotHelper, type: :system
end

Rails アプリケーションの不安定なテストを撲滅したい 〜system spec のデバッグ方法とテストを不安定にさせる要因〜 - あらびき日記」の記事を参考にしました。
これでもう一度テストを実行してみましょう。スクリーンショットがちゃんと表示されるようになるはずです。

テスト開始前にスクリーンショットを削除する

こうやってテストしているとスクリーンショットが溜まっていっちゃいますよね...
ということでテスト実行の直前にスクリーンショットを一度全て削除するようにrails_helper.rbに設定を追加します。

spec/rails_helper.rb
  ...
  RSpec.configure do |config|
    # Driver setting for system tests.
    config.before(:each, type: :system) do
      driven_by :selenium_chrome_headless
    end

    config.before(:each, type: :system, js: false) do
      driven_by :rack_test
    end
+
+   # Delete screenshots before starting new tests
+   config.before(:all) do
+     FileUtils.rm_rf(Rails.root.join('tmp', 'screenshots'), secure: true)
+   end

  config.fixture_path = "#{::Rails.root}/spec/fixtures"
  config.use_transactional_fixtures = true
  config.infer_spec_type_from_file_location!
  config.filter_rails_from_backtrace!
end

これを追加するだけです。config.before(:all)はテスト実行前に1度だけ実行されることを意味しています。
FileUtils.rm_rfはRubyプログラムとしてrm -rfをやるためのモジュールです。
【Rails】RSpecのSystem Test実行前に前回テスト時のScreenshotを削除しておく - Qiita

これでスクリーンショットが溢れかえる心配がなくなりました♪

(オプション)iTerm2でスクリーンショットを表示する

ちょっと裏技みたいな感じですが、iTerm2の場合、webコンテナにとある環境変数を与えるとテストNGのタイミングでiTerm2上にスクリーンショットを表示してくれるようになります。

docker-compose.yml
  version: '3'

  services:
    db:
      image: postgres:12.1-alpine
      environment:
        - TZ=Asia/Tokyo
      volumes:
        - ./tmp/db:/var/lib/postgresql/data

    web:
      build: .
      volumes:
        - .:/app
      ports:
        - 3000:3000
      depends_on:
        - db
+     environment:
+       - RAILS_SYSTEM_TESTING_SCREENSHOT=inline

たったこれだけ。この環境変数を適用するために一度コンテナを再起動してRSpecを実行してみましょう。

# exit
$ docker-compose down
$ docker-compose up -d
$ docker-compose exec web ash
# rspec spec/system/sample_spec.rb

image.png
ちゃんとスクリーンショットが表示されていますね。
RSpecのスクリーンショットはどのファイルなのか探すのが面倒だったりするので、テストシナリオと紐づいてiTerm2で表示してくれるのは非常に助かります。

本日はここまでにしましょう!

後片付け

では後片付けしていきますー。
今回は特にDBにデータも保存していないのでDBを初期化する必要もありませんね。
そういえば、RSpecはテスト実行時に保存されたデータはテストシナリオごとに削除されるようになっているので、テストコード内でデータを保存したとしてもDBの初期化は必要ないんです。(しかもRSpecでテストをする場合、勝手にRAILS_ENVtestで実行されるので、developmentのDBを汚染することもないんです。)

と、いうことで今回はコンテナだけ落として終了です!

# exit 
$ docker-compose down

あ、あと、sample_spec.rbは今回のサンプル用だったので消しておきましょー。
あ、ついでにスクリーンショットも。

$ rm spec/system/sample_spec.rb
$ rm tmp/screenshots/*

まとめ

今回はTDD/BDDを説明させていただきました。
RedGreenリファクタリング の流れと、
GivenWhenThen で受入条件を考えていく方法を紹介しましたね。

さらにテスト自動化を実現するために、
RSpecを導入しました。(SeleniumCapybaraも)
そして初めてのテストコードを書いて実行することができましたね!

次回は、実際にここまでつくってきたアプリのテストコードをコーディングします。
その中でテストコードの書き方を覚えていきましょう!

では、次回も乞うご期待!ここまでお読みいただきありがとうございました!

本日のソースコード

Reference

Other Hands-on Links

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