sioaji2012のブログ

普段は組み込み開発でC言語のみです。主にプログラムや勉強日記です

Redmineプラグインもくもく会 山梨 #005回 プラグイン作成試し

これまでの開発環境を駆使するには、DockerやRailsなど詳しく勉強する必要があるかもしれませんが、一気にやっても多分忘れてしまうので(という理由で)後回しにしつつ、以前ハンズオンでちんぷんかんぷんだったものをトレースしながら、プラグイン作成にそって勉強を少しずつしていこうと思います。

agileware.connpass.com

それでは、こちらのハンズオンを思い出していきたいと思います。

プラグインを作成する

プラグインチュートリアルでは、ジェネレータでプラグインの雛形を作る方法が紹介されているのですが、ジェネレータの情報アップデートがいまいちらしく、失敗する事があるので信用していないという事でした。(ベタに作成することになります)

あと、ハンズオンはRedmine4に向けたハンズオンなので、今回の3.3.2環境ではうまくいかない可能性あり、途中でギブになる可能性あります。(^_^;)

題材は、『いいね!ボタン』です。

プラグインフォルダを作成

Dockerの開発環境用Redmineを起動します。

$ REDMINE_VERSION=3.3.2 docker-compose build --no-cache
$ docker-compose up -d 

作ったドッカー内に入ります。
■ docker exec -it 名前など /bin/bash
・-i : Interactive
・-t: terminal

$ docker exec -it redmine_sqlite3 /bin/bash

中に入れました。

root@ac584a652c94:/tmp/redmine# ls
CONTRIBUTING.md  Gemfile.lock  Rakefile  appveyor.yml  config     db   extra  lib  plugins  script  tmp
Gemfile      README.rdoc   app   bin           config.ru  doc  files  log  public   test

プラグイン用フォルダ『redmine_iine』を作成します。

cd plugins
mkdir redmine_iine

init.rbを作成

プラグインが最初に読み込むものを作成します。

ここからは、MAC側からソース修正してみます。 
Dockerfileと同じフォルダにpluginsフォルダを作ってあって、このフォルダがDockerコンテナ内のpluginsフォルダとの共有フォルダになっています。
(docker-compose.ymlで設定してる)

ctrl+p -> ctrl+q で抜けるとdockerコンテナは起動したまま、抜けられます。

ctrl+p
ctrl+q

MAC側のpluginsフォルダの中には、先程コンテナ内で作成した『redmine_iine』フォルダが作成されているのがわかると思います。

テキストエディタ等で、『init.rb』ファイルを作成します。

init.rb

Redmine::Plugin.register :redmine_iine do
  name 'Redmine いいね!Plugin'
  author 'sioaji'
  version '0.0.1'
end

ここで、通常ですと、プラグインを再読込のために、
railsサーバーをCtrl-C停止後、再起動するのですが、
現状のDocker構成でのやり方がわかりませんでした。

$ docker-compose restart
とか
$ docker stop redmine_sqlite3
$ docker start redmine_sqlite3
とかやっても
再起動されたコンテナがすぐに終了してしまう

困って、ググりました。

dev.classmethod.jp

unskilled.site

けど、良くわからず、、、一応ですが、

docker-compose.ymlを修正する事にしました。

★★★マークの行追加。

version: '3'
services:
  web:
    build:
      context: .
      args:
        REDMINE_VERSION: $REDMINE_VERSION
    image: redmine_sqlite3
    container_name: redmine_sqlite3
    command: >
      bash -c "bundle exec rake redmine:plugins:migrate &&
           bundle exec rails s -p 3000 -b '0.0.0.0'"
    environment:
      RAILS_ENV: development
    volumes:
      - ./plugins:/tmp/redmine/plugins
    ports:
      - "3000:3000"
    stdin_open: true ★★★
    tty: true ★★★

stdin_open: 標準入力
tty: 端末の付与

相当するdocker runオプション −it と同等になる様で、
こうすると、すぐにコンテナが終了しないっぽい。
(よく理解できていないです)

この構成でイメージ作り直して起動します。

$ REDMINE_VERSION=3.3.2 docker-compose build --no-cache
$ docker-compose up -d 

再起動する

docker-compose restart

で再起動した感じになる。
pluginのマイグレートとサーバーの起動するのかな。
二重起動とか心配。
(必ずうまくいくわけでは無さそう。すぐに終了しないでうまくいく場合がおおい感じ)

・・・ひとまずこのやり方で進めます。(^_^;)

ここまでの結果

f:id:sioaji2012:20180530234641p:plain


今回は、ここまで。ほとんど進まず(^_^;) 
次回は、いいね!ボタン表示とhookを少し。な感じでしょうか。

先は長いかも。。です。。。


Redmineプラグインもくもく会 山梨 #004回 開発環境

(まだ)開発環境の続きです。(遅い・・・(^_^;) )

DockerCompose

・Dockerツール群の一つであるDockerComposeを使うと、コンテナ立ち上げ設定をファイル化(yml形式)して管理することが可能になります。
・docker-compose で起動する場合は、Dockerfileを使ってイメージを作成します。

Docker Composeについてもほんの少し(汗)勉強しました。

Docker お勉強 04 Docker Compose - sioaji2012のブログ

対象の docker-compose.yml はこちら。

version: '3'
services:
  # start service for redmine with plugin
  # 1. $ docker-compose build --force-rm --no-cache
  # 2. $ docker-compose up -d
  #
  # If you want to change Redmine's version,
  #
  # 1. $ REDMINE_VERSION=master docker-compose build --force-rm --no-cache
  # 2. $ docker-compose up -d
  #
  #
  web:
    build:
      context: .
      args:
        REDMINE_VERSION: $REDMINE_VERSION
    image: redmine_sqlite3
    container_name: redmine_sqlite3
    command: >
      bash -c "bundle exec rake redmine:plugins:migrate &&
           bundle exec rails s -p 3000 -b '0.0.0.0'"
    environment:
      RAILS_ENV: development
    volumes:
      - ./plugins:/tmp/redmine/plugins
    ports:
      - "3000:3000"

勉強不足ですが、少しだけ説明書いてみます。

★今の最新は compose Ver3
version: '3'

★『WEB』というサービス名となる
services:
  web:

    ★構築時に適用するオプションを指定
    build:

      ★Dockerfile があるパス(や Git URL)を指定 → カレントDIR
      context: .

      ★構築時の引数指定
      args:
        REDMINE_VERSION: $REDMINE_VERSION

    ★ローカルのimageから検索して、なけれ DockerHubからの取得を試みる。
   (buildを指定した場合は構築する)
    image: redmine_sqlite3

    ★カスタム・コンテナ名
    container_name: redmine_sqlite3

    ★デフォルトのコマンドを上書きする
    ★DockerFileはrailsサーバー起動のみで、
     こちらはpluginマイグレードを追加したコマンドにしてる。
    ★docker run だとプラグインなしでRedmine起動。
     composeはプラグインつき起動になる。
    command: >
      bash -c "bundle exec rake redmine:plugins:migrate &&
           bundle exec rails s -p 3000 -b '0.0.0.0'"

    ★環境変数を追加する
    environment:
      RAILS_ENV: development

    ★コンテナ内にマウントするボリュームを指定する( ホスト:コンテナ )
    ★コンテナ内は、Redmineの標準pluginsフォルダ
    ★ホスト側は、カレントDirにpluginsというフォルダを作っている。
     MAC側でこの中にpluginソースを配置して開発する。
    volumes:
      - ./plugins:/tmp/redmine/plugins

    ★公開用のポートを指定する(ホスト側:コンテナ側)
    ports:
      - "3000:3000"

実行オプション docker-compose build

REDMINE_VERSION=3.3.2 docker-compose build --no-cache
docker-compose up -d

こちらも少しだけ説明してみます。

★引数を指定 REDMINE_VERSION=3.3.2
★構築する build
 オプション:
 --force-rm  常に中間コンテナを削除
 --no-cache  構築時にイメージのキャッシュを使わない ★こちら
 --pull      常に新しいバージョンのイメージ取得を試みる
REDMINE_VERSION=3.3.2 docker-compose build --no-cache

★サービス用のコンテナの構築、作成、起動、アタッチを行う:up
 オプション:
 -d  デタッチド・モード: バックグラウンドでコンテナを実行し、新しいコンテナ名を表示
docker-compose up -d

Dockerファイルを基本に適時Compose側の設定で構築・起動していく感じでしょうか。

以上が、DockerComposeの簡単な説明になります。
(もし余裕が出来たらDockerをもっと勉強しなくては。。。その前にRuby On Railsもですが。。。)

build結果

最後の方のログ (Dockerfileのdb:migrate終わったあたりから)
コマンドのステップありますが、実行されていない状態だと思います。

== 20160529063352 AddRolesSettings: migrated (0.0005s) ========================

Removing intermediate container 244ba24cd9b2
 ---> f7771e68733e
Step 13/15 : RUN bundle exec rake generate_secret_token
 ---> Running in 81f23bf15e13
Removing intermediate container 81f23bf15e13
 ---> 4e49572fc819
Step 14/15 : CMD bundle exec rails s -p 3000 -b '0.0.0.0'
 ---> Running in 0fc6f20e4369
Removing intermediate container 0fc6f20e4369
 ---> 41e55eadecaa
Step 15/15 : EXPOSE 3000
 ---> Running in 8b167069180b
Removing intermediate container 8b167069180b
 ---> 2315b3a0cca7
Successfully built 2315b3a0cca7
Successfully tagged redmine_sqlite3:latest

起動 (docker-compose up -d)

mbp:redmine_my_plugin sioaji$ docker-compose up -d
WARNING: The REDMINE_VERSION variable is not set. Defaulting to a blank string.
Recreating redmine_sqlite3 ... done

起動したコンテナの確認(docker ps)

mbp:redmine_my_plugin sioaji$ docker ps

CONTAINER ID        IMAGE               COMMAND                  CREATED
c288f733f5dc        redmine_sqlite3     "bash -c 'bundle exe…"   About a minute ago

STATUS              PORTS                    NAMES
Up About a minute   0.0.0.0:3000->3000/tcp   redmine_sqlite3

注意する事がありました。

同じことを何回もやると、古いイメージデータが名無しで残り続ける(HDD容量減る)ので注意。  
 
今のイメージ(ID = 2315b3a0cca7)

mbp:redmine_my_plugin sioaji$ docker images

REPOSITORY        TAG        IMAGE ID         CREATED          SIZE
redmine_sqlite3   latest     2315b3a0cca7     16 minutes ago   937MB

 
2回目のイメージ(ID = 420c17967e35)
ID = 2315b3a0cca7 は名無しで残り続ける。(タグがついていない)

mbp:redmine_my_plugin sioaji$ docker images

REPOSITORY        TAG        IMAGE ID         CREATED          SIZE
redmine_sqlite3   latest     420c17967e35     4 minutes ago    937MB
<none>            <none>     2315b3a0cca7     23 minutes ago   937MB

不要なイメージを消す

docker rmi イメージID

$ docker rmi 2315b3a0cca7

もくもくで使いそうなDockerコマンド

殴り書きですが、使いそうなコマンドを書いておきます。

http://localhost:3000
------------------------------------------------------------
/* 基本 */
------------------------------------------------------------
$ docker-compose build
$ docker-compose up -d

------------------------------------------------------------
/* バージョン指定 */
------------------------------------------------------------
Dockerfile FROM ruby:2.2.2

$ REDMINE_VERSION=3.3.2 docker-compose build --no-cache
$ docker-compose up -d

------------------------------------------------------------
/* 構築済みのイメージを起動する */
------------------------------------------------------------
■ docker start コンテナ
$ docker start redmine_sqlite3

■ docker stop コンテナ

------------------------------------------------------------
/* 作ったドッカー内に入る */
------------------------------------------------------------
■ docker exec -it 名前など /bin/bash
-i : Interactive
-t: terminal 

$ docker exec -it redmine_sqlite3 /bin/bash

------------------------------------------------------------
/* コンテナを外から止めるか中から止めるか */
------------------------------------------------------------
■ 中から止める
exitでコンテナから抜けた時のコンテナ起動→接続

$ sudo docker start <コンテナID>
$ sudo docker attach <コンテナID>

ctrl+p -> ctrl+q で抜けるとdockerコンテナは起動したまま、抜けられる
$ sudo docker attach <コンテナID>
で再入場


■ 外から止める
$ sudo docker stop <コンテナID>
$ sudo docker start <コンテナID>

$ sudo docker stop redmine_sqlite3

------------------------------------------------------------
/* 作業中のコンテナをイメージとして保存する */
------------------------------------------------------------
■ docker commit 名前など 保存イメージ名

$ docker commit redmine_sqlite3 create_ticket

⬇
イメージ一覧で確認
$ docker images

------------------------------------------------------------
/* コンテナ消す */
------------------------------------------------------------
$ docker rm コンテナIDとか名前していする

- docker start: コンテナを起動する
- docker stop : コンテナを止める(コンテナプロセスにSIGTERMを送信して一定時間経過後にSIGKILLを送信、デフォルトは10秒後)
- docker kill : コンテナを止める(直ちにSIGKILLを送信)
- docker rm : 停止状態のコンテナを削除 
- docker rm -f : 起動状態のコンテナも削除 
- docker ps -aq | xargs docker rm -f : 全部のコンテナをまとめてrm
  or
- docker rm -f 'docker -aq'

------------------------------------------------------------
/* イメージ消す */
------------------------------------------------------------
$ docker rmi <IMAGE ID> 

■未使用イメージ一括削除
$ docker image prune

■タグがついていないイメージをすべて削除する
docker rmi $(docker images -f 'dangling=true' -q)

------------------------------------------------------------
/* docker run */
------------------------------------------------------------
■名前つける
$ sudo docker run -it --name infra-workshop-sioaji centos
$ sudo docker rename 変更前の名前 変更後お名前

■docker run -v のお話
-v : volume

$ sudo docker run -it --name "mamy1326" -v /home/psuser:/home/test centos
[option] image [command] を守ればオプションの順番は問われないと思いますー
イメージ名は最後

参考:Docker の Data Volume まわりを整理する
https://qiita.com/lciel/items/e21a4ede3bac7fb3ec5a

■ 起動したコンテナのIPを知らべる
# for cname in $(docker ps |grep -v NAMES|awk '{print $1}'); do echo -n "${cname} ";docker inspect -format="{{ .NetworkSettings.IPAddress }}" ${cname}; done

00c5ccb98f17 ormat=172.17.0.8
39d0cd55f2f3 ormat=172.17.0.7
bf091cc5eafa ormat=172.17.0.6
4b2898c6f5a0 ormat=172.17.0.5
08d6607d8676 ormat=172.17.0.4
cb8719479fd3 ormat=172.17.0.3
dac5fd5ff2e9 ormat=172.17.0.2



今回は以上でした。
このままで、コンテナのrailsを再起動する方法がわかりませんでした。どうやってやるのかな。
developmentなので、再起動不要らしいのですが。。。

次回からは、以前ハンズオンでちんぷんかんぷんだったプラグインを写経してみようと思います。


Redmineプラグインもくもく会 山梨 #003回 開発環境

前回、plugin開発用のRedmineが起動できる様になりました。

qiita.com

こちらのページで、akikoさんが丁寧に説明されているので、説明は不要と思いますが、もくもくの覚え書きとして、僕なりの解釈で説明を書いてみようと思います。

と言いましても、結局Dockerfileを初めて読みまして、ちんぷんかんぷん。でしたので、

Dockerfile 少し勉強しました。

だいたい写経で勉強した次の日には、忘れてしまいます。(^_^;)

なので、ひとまず、もくもくで自分用に書き換えたDockerfileから説明を書いていきます。


その前に、

注意すること

がありました。皆さんはご存知と思いますが。。。

Dockerfileの中で、RUN でコマンドを実行するたびにイメージがコミットされて、 次回、Dockerfileを実行した時は、そのキャッシュが使われる。

つまり、Dockerfileを書き換えても、前のRUN行は再実行されない。


例で説明しますと、

1回目。updateとinstallは実行される。

FROM ubuntu:14.04
RUN apt-get update
RUN apt-get install -y curl ★この行を修正する

書き換え後、

2回目。updateはキャッシュが使われ古いupdate結果が使われる。(updateは再実行されない)

FROM ubuntu:14.04
RUN apt-get update
RUN apt-get install -y curl nginx ★この行を修正した

キャッシュを使わないでDockerfile実行する設定にすれば、良いですが時間がかかる。
こう書くのがおすすめっぽい。

FROM ubuntu:14.04
RUN apt-get update && apt-get install -y curl nginx

皆さん、Dockerをどういう用途で使っているのか気になりますが、

もくもくの説明に入りたいと思います。

.-------.
★が説明
.-------.

------------------------------------------
★ruby:2.1.10のイメージをベースにして作成
------------------------------------------
FROM ruby:2.1.10

------------------------------------------
★ラベル(説明省略)
------------------------------------------
LABEL maintainer="K.SUZUKI / (Twitter: @KuniSioaji)" \
  description="Image to run Redmine simply with sqlite to try/review plugin."

------------------------------------------
★コメント(説明省略)
------------------------------------------
#
# You can run target version of Redmine.
# If you use this Dockerfile only, try this:
#    $ docker build -t redmine_stable .
#    $ docker run -d -p 3000:3000 redmine_stable
#
# You can change Redmine version with arg
#    $ docker build --rm --build-arg REDMINE_VERSION=master -t redmine_master .
#    $ docker run -d -p 3000:3000 redmine_master
#

------------------------------------------
★構築(build)時の引数(変数)を指定しなかった時のデフォルト値
★引数でRedmineのバージョンを渡す事で、
 構築するRedmineのバージョンを指定可能にしている。
------------------------------------------
### get Redmine source
ARG REDMINE_VERSION=3.4-stable

------------------------------------------
★Dockerfileのビルドはbashではなくshで実行されるためエラーが出る。
 shをbashへのシンボリックリンクで置き換えることで対応する。
★echo :引数の表示
------------------------------------------
### Replace shell with bash so we can source files ###
RUN rm /bin/sh && ln -s /bin/bash /bin/sh && echo "REDMINE_VERSION: ${REDMINE_VERSION}"

------------------------------------------
★最低限必要なパッケージを追加
------------------------------------------
### install default sys packeges ###
RUN apt-get update && apt-get install -qq -y \
    git vim            \
    sqlite3

------------------------------------------
★Redmineのソースを /tmp/ ディレクトリにgit cloneする 
 (引数でタグバージョン指定してバージョン変更)
★コメント文の方は、安定版(ブランチ)をcloneする場合の記述
★clone先は、/tmp/redmine_coディレクトリとなる。
------------------------------------------
#RUN cd /tmp && git clone --depth 1 -b ${REDMINE_VERSION} https://github.com/redmine/redmine redmine
RUN cd /tmp && git clone --depth 1 -b ${REDMINE_VERSION} --single-branch https://github.com/redmine/redmine redmine_co

------------------------------------------
★引数の表示?
------------------------------------------
RUN echo "REDMINE_VERSION: ${REDMINE_VERSION}"

------------------------------------------
★ WORKDIR
 作業ディレクトリ(/tmp/redmine_coに、cloneしたソースがある)
 以後、このディレクトリでRUN実行されていく。
------------------------------------------
WORKDIR /tmp/redmine_co

------------------------------------------
★SQLite3用のdatabase.ymlをconfig/に作成する
------------------------------------------
RUN echo $'test:\n\
  adapter: sqlite3\n\
  database: /tmp/data_co/redmine_test.sqlite3\n\
  encoding: utf8mb4\n\
\n\
development:\n\
  adapter: sqlite3\n\
  database: /tmp/data_co/redmine_development.sqlite3\n\
  encoding: utf8mb4\n'\
>> config/database.yml

------------------------------------------
★bundle installする
------------------------------------------
RUN gem update bundler
RUN bundle install --without mysql postgresql rmagick test

------------------------------------------
★Redmine本体のマイグレーションをする
------------------------------------------
RUN bundle exec rake db:migrate
RUN bundle exec rake generate_secret_token

------------------------------------------
★CMD:ここまでに作成したイメージで実行するコマンド
    (構築(build)時には、実行しない)
 port 3000で起動する
------------------------------------------
CMD bundle exec rails s -p 3000 -b '0.0.0.0'
EXPOSE 3000

リファレンスより。

※RUN と CMD を混同しないでください。
 RUN が実際に行っているのは、コマンドの実行と結果のコミットです。
 一方の CMD は構築時には何もしませんが、イメージで実行するコマンドを指定します。


今日は、ここまで。 次回は、docker-compose.ymlと、起動などやっていく予定です。


Docker お勉強 04 Docker Compose

もくもくで使わせてもらっている docker-compose.yml が何なのかわからないので調べます。 下記ページを読みながら、覚え書きを記載していきます。

DockerCompose

・Dockerツール群の一つであるDockerComposeを使うと、コンテナ立ち上げ設定をファイル化(yml形式)して管理することが可能になります。
・docker-compose で起動する場合は、Dockerfileを使ってイメージ作成します。

.yml →YAMLファイル →ヤムルファイル

  • データ構造を記述するフォーマットの一つ
  • jsonのスーパーセット(上位互換みたいな感じ)だそう
  • json形式でもymlとして認識してくれるらしい
  • DockerComposeではこのyml形式ファイルを使って、コンテナ起動に関してを記述していきます。
    docker-compose.yml

version:   バージョン番号

Compose file versions and upgrading

現在の最新は '3'

version: '3'

services:   各コンテナに関しての記述を行っていく

services:
  web_co:
  • services構造直下に各コンテナに関しての記述を行っていく。
    次の構造(キー)としては各サービス名となる。(上記:web_co)
  • 立ち上げるコンテナごとに記述内容が異なるはずですので、区分して記述するために構造を分割します。
  • 任意の名前でOK
  • この名前がコンテナの名前になるわけではありません。

build:   構築時に適用するオプションを指定

build で指定できるのは、
・構築用コンテクストのパスを含む文字列
・context の配下にある特定の物(オブジェクト)
・dockerfile のオプション、 引数
build: ./dir

build:
  context: ./dir
  dockerfile: Dockerfile-alternate
  args:
    buildno: 1

【もくもく例】

    build:
      context: .
      args:
        REDMINE_VERSION: $REDMINE_VERSION

context:   Dockerfile があるパスや Git URLを指定

・Dockerfile があるディレクトリのパスや Git リポジトリの URL を指定します。
・値に相対パスを指定したら、Compose ファイルのある場所を基準とした相対パスとして解釈します。
・指定したディレクトリが構築コンテクストとなり、Docker デーモンに送信します。
・Compose は生成時の名前で構築・タグ付けし、それがイメージとなります。
build:
  context: ./dir

【もくもく例】

    build:
      context: .

args:   build のオプション(args)

・構築時に build のオプション(args)を追加します。
・配列でも辞書形式(「foo=bar」の形式)も指定できます。
・ブール演算子(true、false、yes、no)を使う場合はクォートで囲む必要があります。
 そうしないと YAML パーサは True か False か判別できません。
・構築時に引数のキーとして解釈する環境変数の値は、Compose を実行するマシン上のみです。
build:
  args:
    buildno: 1
    user: someuser

build:
  args:
    - buildno=1
    - user=someuser

【もくもく例】

    build:
      args:
        REDMINE_VERSION: $REDMINE_VERSION

image:   コンテナの作成元イメージを指定する

・コンテナを実行時に元となるイメージを指定します。
・リポジトリ名・タグあるいはイメージ ID の一部を指定できます。
image: redis
image: ubuntu:14.04
image: tutum/influxdb
image: example-registry.com:4000/postgresql
image: a4bc65fd
  • すでに存在するimageを探してくれます。
    docker runの時と同じ様にまずローカルのimageから検索して、なければDockerHubからの取得を試みてくれます。
  • イメージが存在していなければ、Compose は pull (取得)を試みます。
    しかし build を指定している場合は除きます。その場合、指定されたタグやオプションを使って構築します。

【もくもく例】

  web_co:
    build:
      context: .
      args:
        REDMINE_VERSION: $REDMINE_VERSION
    image: redmine_co_sqlite3

command:   デフォルトのコマンドを上書きする

デフォルトのコマンドを上書きします。
command: bundle exec thin -p 3000
  • これは Dockerfile の書き方に似せることもできます。
command: [bundle, exec, thin, -p, 3000]

【もくもく例】

    command: >
      bash -c "bundle exec rake redmine:plugins:migrate &&
           bundle exec rails s -p 3000 -b '0.0.0.0'"


⬇Dockerfile側は、こう書いてある⬇

CMD bundle exec rails s -p 3000 -b '0.0.0.0'

(docker run だとプラグインなしでRedmine起動。composeはプラグインつき起動)

environment:   環境変数を追加する

・環境変数を追加します。
・配列もしくは辞書形式(dictionary)で指定できます。
・boolean 値 (true、false、yes、no のいずれか) は、
 YMLパーサによって True や False に変換されないよう、クォートで囲む必要があります。
・キーだけの環境変数は、Compose の実行時にマシン上で指定するものであり、
 シークレット(API鍵などの秘密情報)やホスト固有の値を指定するのに便利です。
environment:
  RACK_ENV: development
  SHOW: 'true'
  SESSION_SECRET:

environment:
  - RACK_ENV=development
  - SHOW=true
  - SESSION_SECRET

【もくもく例】

    environment:
      RAILS_ENV: development

volumes:   コンテナ内にマウントするボリュームを指定する

マウント・パスまたは名前を付けたボリュームは、
・オプションでホストマシン上のパス指定 →( ホスト:コンテナ )
・アクセス・モード →( ホスト:コンテナ:rw )
を指定できます。 

・ホスト上の相対パスをマウント可能です。
・相対パスは Compose 設定ファイルが使っているディレクトリを基準とします。
・相対パスは . または .. で始まります。
volumes:
  # パスを指定したら、Engine はボリュームを作成
  - /var/lib/mysql

  # 絶対パスを指定しての割り当て
  - /opt/data:/var/lib/mysql

  # ホスト上のパスを指定する時、Compose ファイルからのパスを指定
  - ./cache:/tmp/cache

  # ユーザの相対パスを使用
  - ~/configs:/etc/configs/:ro

  # 名前付きボリューム(Named volume)
  - datavolume:/var/lib/mysql

ホスト側のパスを指定せず、 volume_driver を指定したい場合があるかもしれません。

使用するボリュームドライバを指定する
volume_driver: mydriver

【もくもく例】

    volumes:
      - .:/tmp/redmine_co/plugins/redmine_issue_badge

( ホスト:コンテナ )

ports:   公開用のポートを指定する

・公開用のポートです。
・ホスト側とコンテナ側の両方のポートを指定 →( ホスト側:コンテナ側 )
・コンテナ側のポートのみも指定できます(ホスト側はランダムなポートが選ばれます)。

注釈
* ホスト側:コンテナ側 の書式でポートを割り当てる時、コンテナのポートが 60 以下でエラーが発生します。
これは YAML が xx:yy 形式の指定を、60 進数(60が基準)の数値とみなすからです。
そのため、ポートの割り当てには常に文字列として指定することを推奨します。

ports:
 - "3000"
 - "3000-3005"
 - "8000:8000"
 - "9090-9091:8080-8081"
 - "49100:22"
 - "127.0.0.1:8001:8001"
 - "127.0.0.1:5000-5010:5000-5010"

【もくもく例】

    ports:
      - "3000:3000"

実行オプション docker-compose build

【もくもく例】

 REDMINE_VERSION=master docker-compose build --force-rm --no-cache

rm:   停止済みのサービス・コンテナを削除する

使い方: rm [オプション] [サービス...]

オプション:
    -f, --force   確認なく削除する
    -v            コンテナにアタッチしているアノニマス・ボリュームも削除
    -a, --all     docker-compose run で作成した一度だけのコンテナを全て削除
                  docker-compose run
デフォルトでは、コンテナにアタッチしている匿名ボリューム(anonymous volume)を削除しません。
ボリュームを削除するには -v オプションを使います。
全てのボリュームを表示するには docker volume ls を使います。
(明示的に削除しなければ)ボリューム内にあるデータは失われません。

build:   構築する

使い方: build [オプション] [サービス...]

オプション:
--force-rm  常に中間コンテナを削除
--no-cache  構築時にイメージのキャッシュを使わない
--pull      常に新しいバージョンのイメージ取得を試みる
  • サービスは プロジェクト名_サービス として構築時にタグ付けられます。 例: composetest_db 。
  • サービスの Dockerfile や構築ディレクトリの内容に変更を加える場合は、 docker-compose build で再構築を実行します。

実行オプション docker-compose up

【もくもく例】

docker-compose up -d

up:   サービス用のコンテナの構築、作成、起動、アタッチを行う

使い方: up [オプション] [サービス...]

オプション:
    -d                         デタッチド・モード: バックグラウンドでコンテナを実行し、新しいコンテナ名を表示
                               --abort-on-container-exit と同時に使えない
    --no-color                 白黒で画面に表示
    --no-deps                  リンクしたサービスを表示しない
    --force-recreate           設定やイメージに変更がなくても、コンテナを再作成する
                               --no-recreate と同時に使えません
    --no-recreate              コンテナが既に存在していれば、再作成しない
                               --force-recreate と同時に使えない
    --no-build                 イメージが見つからなくても構築しない
    --build                    コンテナを開始前にイメージを構築する
    --abort-on-container-exit  コンテナが1つでも停止したら全てのコンテナを停止
                               -d と同時に使えない
    -t, --timeout TIMEOUT      アタッチしている、あるいは既に実行中のコンテナを
                               停止する時のタイムアウト秒数を指定 (デフォルト:10 )
    --remove-orphans           Compose ファイルで定義されていないサービス用のコンテナを削除
  • 既に実行している場合は、このコマンドによってリンクされているサービスも起動します。
  • docker-compose up コマンドは各コンテナの出力を統合します。
  • コマンドを終了(exit)すると、全てのコンテナを停止します。
  • docker-compose up -d で実行すると、コンテナをバックグラウンドで起動し、実行し続けます。
  • もしサービス用のコンテナが存在している場合、かつ、コンテナを作成後にサービスの設定やイメージを変更している場合は、 docker-compose up -d を実行すると、 設定を反映するためにコンテナを停止・再作成します(マウントしているボリュームは、そのまま保持します)。
  • Compose が設定を反映させないようにするには、 --no-recreate フラグを使います。
  • Compose で処理時、強制的に全てのコンテナを停止・再作成するには --force-recreate フラグを使います。

ひとまず、ここまで。

【もくもくのdocker-compose.yml】

version: '3'
services:
  # start service for redmine with plugin
  # 1. $ docker-compose build --force-rm --no-cache
  # 2. $ docker-compose up -d
  #
  # If you want to change Redmine's version,
  #
  # 1. $ REDMINE_VERSION=master docker-compose build --force-rm --no-cache
  # 2. $ docker-compose up -d
  #
  #
  web_co:
    build:
      context: .
      args:
        REDMINE_VERSION: $REDMINE_VERSION
    image: redmine_co_sqlite3
    container_name: redmine_co_sqlite3
    command: >
      bash -c "bundle exec rake redmine:plugins:migrate &&
           bundle exec rails s -p 3000 -b '0.0.0.0'"
    environment:
      RAILS_ENV: development
    volumes:
      - .:/tmp/redmine_co/plugins/redmine_issue_badge
    ports:
      - "3000:3000"

Docker お勉強 03 Dockerfile

もくもくで使わせていただいているDockerfileを理解するためにお勉強です。

以下のページを参考に勉強して、もくもくで使ったDockerfileを例にあげて、覚え書きをこちらに記載します。

FROM: ベースイメージの指定

【解説】 Dockerfile リファレンスの FROM 命令 → 本家

可能であれば、自分のイメージの元として現在の公式レポジトリを使います。
私たちは Debian イメージ を推奨します。
これは、非常にしっかりと管理されており、ディストリビューションの中でも最小(現在は 100 MB 以下)になるよう維持されているからです。
FROM <イメージ>
FROM <イメージ>:<タグ>
FROM <イメージ>@<digest>
  • 以降の命令で使う ベース・イメージ を指定する。
  • コメント以外では FROM を一番始めに書く必要がある。
  • 単一の Dockerfile から複数のイメージを作成するため、複数の FROM を指定できる。
    各 FROM 命令ごとに自動的にコミットし、最新のイメージ ID が出力される。
  • タグ や digest 値はオプション。
    省略した場合、ビルダーはデフォルトの latest (最新)とみなす。
    ビルダーは一致する tag 値が無ければエラーを返す。
  • digestは、イメージのユニークな識別子ID (docker images --digestsで表示確認出来る)
    メージ生成後に変更が加えられなければ、digest 値は変更されていないと考えられる。
    v2 以降の形式を使うイメージで使える。

【もくもく例】

FROM ruby:2.1.10

LABEL: コンテナとイメージに付与できるラベル

【解説】Dockerfile リファレンスの LABEL 命令 → 本家

LABEL 命令はイメージにメタデータを追加します。 
LABEL はキーとバリューのペアです。 
LABEL の値に空白スペースを含む場合はクォートを使いますし、コマンドラインの分割にバックスラッシュを使います。

MAINTAINER 命令は現在非推奨

LABEL <key>=<value> <key>=<value> <key>=<value> ...

【例】

LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."
  • イメージは複数のラベルを持てます。
    可能であれば1つの LABEL にすることをお勧めします。
    複数のラベルを指定したら、各 LABEL 命令は新しいレイヤを準備しますが、多くのラベルを使えば、それだけレイヤを使います。
    次の例は1つのイメージ・レイヤを使うものです。
LABEL multi.label1="value1" multi.label2="value2" other="value3"

上記の例は、次のようにも書き換えられます。

LABEL multi.label1="value1" \
      multi.label2="value2" \
      other="value3"
  • ラベルには、FROM イメージが使う LABEL も含まれています。
    ラベルのキーが既に存在している時、Docker は特定のキーを持つラベルの値を上書きします。
  • イメージが使っているラベルを確認するには、 docker inspect コマンドを使います。

【もくもく例】

LABEL maintainer="K.SUZUKI / (Twitter: @KuniSioaji)" \
  description="Image to run Redmine simply with sqlite to try/review plugin."

ARG: docker build コマンドで使う変数

【解説】Dockerfile リファレンスの ARG 命令 → 本家

ARG 命令は、構築時に作業者が docker build コマンドで使う変数
ARG <名前>[=<デフォルト値>]
  • --build-arg <変数名>=<値> フラグを定義するものです。
    ユーザが構築時に引数を指定しても Dockerfile で定義されていなければ、構築時に次のようなエラーが出ます。
    One or more build-args were not consumed, failing build.
  • 複数の ARG を指定可能です。
  • ARG 命令のデフォルト値を指定できます。
    構築時に値の指定が無ければ、このデフォルト値を使います。
ARG user1=someuser
ARG buildno=1
  • ARG 変数は Dockerfile で記述した行以降で効果があります。ただし、コマンドライン上で引数の指定が無い場合です。

【例】

FROM busybox
USER ${user:-some_user}
ARG user
USER $user
...
$ docker build --build-arg user=what_user Dockerfile

FROM busybox
USER ${user:-some_user}
          → ここまでは、usr=some_user
ARG user
USER $user    → ここからは、usr=what_user (コマンドラインで指定したもの)
...
  • 構築時の変数として、GitHub の鍵やユーザの証明書などの秘密情報を含むのは、推奨される使い方ではありません。

  • ARG や ENV 命令を RUN 命令のための環境変数にも利用できます。
    ENV 命令を使った環境変数の定義は、常に同じ名前の ARG 命令を上書きします。

【例】

FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER v1.0.0
RUN echo $CONT_IMG_VER

or

FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER ${CONT_IMG_VER:-v1.0.0}
RUN echo $CONT_IMG_VER
$ docker build --build-arg CONT_IMG_VER=v2.0.1 Dockerfile

FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER v1.0.0
RUN echo $CONT_IMG_VER →引数を設定した場合、v2.0.1。指定しない場合、v1.0.0
  • 以下のものは、ARG 命令を記載しなくても、ARG 変数で指定出来ます。
    • HTTP_PROXY
    • http_proxy
    • HTTPS_PROXY
    • https_proxy
    • FTP_PROXY
    • ftp_proxy
    • NO_PROXY
    • no_proxy

※これらを使うには、コマンドラインで --build-arg <変数名>=<値> フラグを単に渡すだけです。

構築キャッシュの影響

キャッシュについてはこちらのページをみて少し勉強しました。
Dockerfileを書く時の注意とかコツとかハックとか

ENV 変数は、値が残り続ける。
ARG 変数は、値が残り続けない。
ARG 変数は、構築キャッシュで残り続ける方法として扱える。

ARG 変数の値が以前の値と違う時は、「キャッシュ・ミス」が発生する。
ARG 変数の値を定義していなくても発生します。
特に、すべての RUN 命令は ARG 変数を(環境変数から)暗黙的に使おうとするため、結果としてキャッシュ・ミスを引き起こす。

【例】

--build-arg CONT_IMG_VER=<値> をコマンドライン上で指定

FROM ubuntu
ARG CONT_IMG_VER 
RUN echo $CONT_IMG_VER 
FROM ubuntu
ARG CONT_IMG_VER
RUN echo hello        → キャッシュ・ミス発生

上記の変更は、3行目でキャッシュミス発生。

FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER $CONT_IMG_VER → キャッシュ・ミス発生
RUN echo $CONT_IMG_VER

上記は、ENV コマンドはイメージの中で処理される。キャッシュイメージと値が変わるのでミス発生。

FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER hello → キャッシュ・ミス発生しない
RUN echo $CONT_IMG_VER

上記は、ENV コマンドはイメージの中で処理される。キャッシュイメージは、helloの固定値でミス発生しない。

ENVとの使い分け

Dockerfile ARG入門より

  • ENVでは、コンテナ内で環境変数として変数が定義されます。
    ENVを使用する場合は、CMDやENTRYPOINTによって実行されたコマンドに適している。
  • ARG は、 Dockerfileのbuild時に一時的に変数が定義され、コマンドの実行時に展開されます。
    ARGは、変数を展開した状態でコンテナ内にファイルを配置したい場合に有効です。
  • ARGは変数を展開した状態でコンテナに状態を設定するという特性から、コンテナが管理するファイルには有効ですが、 VOLUMEによって管理されているディレクトリに配置されているファイルに対してはENVを使ったほうが便利な場面が多いです。

【もくもく例】

### get Redmine source
ARG REDMINE_VERSION=3.4-stable

RUN: コマンドの実行

【解説】Dockerfile リファレンスの RUN 命令 → 本家

RUN 命令は既存イメージ上の新しいレイヤで、あらゆるコマンドを実行し、その結果をコミットする命令です。
コミットの結果得られたイメージは、 Dockerfile の次のステップで使われます。
ソース・コントロールのように、イメージの履歴上のあらゆる場所からコンテナを作成可能です。

RUN には2つの形式があります。

RUN <コマンド> 
 ・シェル形式、コマンドを実行する。
 ・Linux 上のデフォルトは /bin/sh -c であり、Windows 上 cmd /S /C。

RUN ["実行バイナリ", "パラメータ1", "パラメータ2"] 
 ・exec 形式
  • exec 形式はシェルの文字列を変更できないようにします。
    また、 指定されたシェル実行環境がベース・イメージに含まれなくても RUN コマンドを使えます。
  • デフォルトの shell のシェルを変更するには SHELL コマンドで変更できます。
  • シェル 形式では、RUN 命令を \ (バックスラッシュ)を使い、次の行と連結します。
  • RUN 命令によるキャッシュは自動的に無効化できません。
    RUN apt-get dist-upgrade -y のような命令のキャッシュがあれば、次の構築時に再利用されます。
    RUN 命令でキャッシュを使いたくない場合は、 --no-cache フラグを使います。
    例: docker build --no-cache .

一般的な利用例は apt-get アプリケーションです。
RUN apt-get コマンドは、パッケージをインストールするので、気をつけるべきいくつかの了解事項があります。

RUN apt-get update や dist-upgrade を避けるべきでしょう。
ベース・イメージに含まれる「必須」パッケージの多くが、権限を持たないコンテナの内部で更新されないためです。 もし、ベース・イメージに含まれるパッケージが時代遅れになっていれば、イメージのメンテナに連絡すべきでしょう。
たとえば、 foo という特定のパッケージを知っていて、それを更新する必要があるのであれば、自動的に更新するために apt-get install -y foo を使います。

RUN apt-get updaate と apt-get install は常に同じ RUN 命令文で連結します。以下は実行例です。

RUN apt-get update && apt-get install -y \
    package-bar \
    package-baz \
    package-foo

RUN 命令文で apt-get update だけを使うとキャッシュ問題を引き起こし、その後の apt-get install 命令が失敗します。
例えば、次のように Dockerfile を記述したとします。

FROM ubuntu:14.04
RUN apt-get update
RUN apt-get install -y curl

イメージを構築すると、全てのレイヤは Docker にキャッシュされます。
次に、別のパッケージを追加する apt-get install を編集したとします。

FROM ubuntu:14.04
RUN apt-get update
RUN apt-get install -y curl nginx

Docker は冒頭からファイルを読み込み、命令の変更を認識すると、前のステップで作成したキャッシュを再利用します。しかし、 apt-get update は 決して 実行されず、キャッシュされたバージョンを使います。これは apt-get update が実行されていないためです。そのため、古いバージョンの curl と nginx パッケージを取得する恐れがあります。

そこで Dockerfile でのインストールには RUN apt-get update && apt-get install -y を使うことで、最新バージョンのパッケージを、追加の記述や手動作業なく利用できます。

RUN apt-get update && apt-get install -y \
    package-bar \
    package-baz \
    package-foo=1.3.*

バージョンを指定すると、何がキャッシュされているか気にせずに、特定バージョンを取得した上での構築を強制します。このテクニックは、必要なパッケージの予期しない変更によって引き起こされる失敗を減らします。

以下は 推奨する apt-get の使い方の全てを示すものです。

RUN apt-get update && apt-get install -y \
    aufs-tools \
    automake \
    build-essential \
    curl \
    dpkg-sig \
    libcap-dev \
    libsqlite3-dev \
    lxc=1.0* \
    mercurial \
    reprepro \
    ruby1.9.1 \
    ruby1.9.1-dev \
    s3cmd=1.1.* \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*
  • s3cmd 命令行は、バージョン 1.1.* を指定します。
    従来のイメージが古いバージョンを使っていたとしても、新しいイメージは apt-get update でキャッシュを使わないので、確実に新しいバージョンをインストールします。
  • 各行はパッケージのリストであり、パッケージの重複という間違いをさせないためです。
  • 付け加えると、apt キャッシュをクリーンにし、 /var/lib/apt/lits を削除することで、イメージのサイズを減らします。
  • RUN 命令は apt-get update から開始されるので、 apt-get install でインストールされるパッケージは、常に新鮮なものです。

【もくもく例】

1)
FROM ruby:2.1.10
LABEL maintainer="K.SUZUKI / (Twitter: @KuniSioaji)" \
  description="Image to run Redmine simply with sqlite to try/review plugin."

#
# You can run target version of Redmine.
# If you use this Dockerfile only, try this:
#    $ docker build -t redmine_stable .
#    $ docker run -d -p 3000:3000 redmine_stable
#
# You can change Redmine version with arg
#    $ docker build --rm --build-arg REDMINE_VERSION=master -t redmine_master .
#    $ docker run -d -p 3000:3000 redmine_master
#

2)
### get Redmine source
ARG REDMINE_VERSION=3.4-stable
3)
### Replace shell with bash so we can source files ###
RUN rm /bin/sh && ln -s /bin/bash /bin/sh && echo "REDMINE_VERSION: ${REDMINE_VERSION}"

4)
### install default sys packeges ###
RUN apt-get update && apt-get install -qq -y \
    git vim            \
    sqlite3
5)
RUN cd /tmp && git clone --depth 1 -b ${REDMINE_VERSION} --single-branch https://github.com/redmine/redmine redmine_co
RUN echo "REDMINE_VERSION: ${REDMINE_VERSION}"
WORKDIR /tmp/redmine_co
6)
RUN echo $'test:\n\
  adapter: sqlite3\n\
  database: /tmp/data_co/redmine_test.sqlite3\n\
  encoding: utf8mb4\n\
\n\
development:\n\
  adapter: sqlite3\n\
  database: /tmp/data_co/redmine_development.sqlite3\n\
  encoding: utf8mb4\n'\
>> config/database.yml
7)
RUN gem update bundler
RUN bundle install --without mysql postgresql rmagick test
RUN bundle exec rake db:migrate
RUN bundle exec rake generate_secret_token
CMD bundle exec rails s -p 3000 -b '0.0.0.0'
EXPOSE 3000
  1. ベースのイメージは、ruby2.1.10にしています。
  2. ARG REDMINE_VERSION というところで、デフォルトでRedmine最新バージョンの安定版を指定
  3. Dockerfileのビルドはbashではなくshで実行されるためエラーが出る。
    shをbashへのシンボリックリンクで置き換えることで対応する。
  4. 最低限必要なパッケージを追加
  5. Redmineのソースを /tmp/ ディレクトリにgit cloneする (引数でタグバージョン指定してバージョン変更)
  6. SQLite3用のdatabase.ymlを作成する
  7. bundle installする
    Redmine本体のマイグレーションをする
    port 3000で起動する

WORKDIR: 作業ディレクト

【解説】Dockerfile リファレンスの WORKDIR 命令 → 本家

WORKDIR 命令セットは Dockerfile で RUN 、 CMD 、 ENTRYPOINT 、 COPY 、 ADD 命令実行時の作業ディレクトリ(working directory)を指定します。
もし WORKDIR が存在しなければ、 Dockerfile 命令内で使用しなくてもディレクトリを作成します。

明確さと信頼性のため、常に WORKDIR からの絶対パスを使うべきです。
また、 WORKDIR を RUN cd ... && 何らかの処理 のように増殖する命令の代わり使うことで、より読みやすく、トラブルシュートしやすく、維持しやすくします。
WORKDIR /path/to/workdir
  • 1つの Dockerfile で複数回の利用が可能です。
    パスを指定したら、 WORKDIR 命令は直前に指定した相対パスに切り替えます。

【例】

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

この Dockerfile を使えば、最後の pwd コマンドの出力は /a/b/c になります。

WORKDIR 命令は ENV 命令を使った環境変数も展開できます。
環境変数を使うには Dockerfile で明確に定義する必要があります。

【例】

ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd

この Dockerfile を使えば、最後の pwd コマンドの出力は /path/$DIRNAME になります。

【もくもく例】

RUN cd /tmp && git clone --depth 1 -b ${REDMINE_VERSION} --single-branch https://github.com/redmine/redmine redmine_co
RUN echo "REDMINE_VERSION: ${REDMINE_VERSION}"
WORKDIR /tmp/redmine_co

CMD: docker runの際に起動するコマンド

【解説】Dockerfile リファレンスの CMD 命令 → 本家

Dockerfile で CMD 命令を一度だけ指定できます。
複数の CMD がある場合、最も後ろの CMD のみ有効です。

CMD の主な目的は、 コンテナ実行時のデフォルトを提供します 。 
デフォルトには、実行可能なコマンドが含まれているか、あるいは省略されるかもしれません。
省略時は ENTRYPOINT 命令で同様に指定する必要があります。

RUN と CMD を混同しないでください。 
RUN が実際に行っているのは、コマンドの実行と結果のコミットです。
一方の CMD は構築時には何もしませんが、イメージで実行するコマンドを指定します。

CMD には3つの形式があります。

■exec 形式、推奨する形式
CMD ["実行バイナリ", "パラメータ1", "パラメータ2"] 

■ENTRYPOINT のデフォルト・パラメータ
CMD ["パラメータ1", "パラメータ2"] 

■シェル形式
CMD <コマンド>
  • シェルあるいは exec 形式を使う時、 CMD 命令はイメージで実行するコマンドを指定します。
  • CMD を シェル 形式で使えば、 <コマンド> は /bin/sh -c で実行されます。

【例】

FROM ubuntu
CMD echo "This is a test." | wc -
  • <コマンド>をシェルを使わずに実行 したい場合、コマンドを JSON 配列で記述し、実行可能なフルパスで指定する必要があります。
    配列の形式が CMD では望ましい形式です 。
    あらゆる追加パラメータは個々の配列の文字列として指定する必要があります。

【例】

FROM ubuntu
CMD ["/usr/bin/wc","--help"]
  • もしコンテナで毎回同じものを実行するのであれば、 CMD と ENTRYPOINT の使用を検討ください。
    詳細は ENTRYPOINT をご覧ください。
  • ユーザが docker run で引数を指定した時、これらは CMD で指定したデフォルトを上書きします。

【補足】

CMD 命令は、イメージに含まれるソフトウェアの実行と、その引数のために使うべきです。
CMD は常に CMD [“executable”, “param1”, “param2”…] のような形式で使うべきです。

そのため、イメージがサービス向け(Apache、Rails 等)であれば、 CMD ["apache2","-DFOREGROUND"] のようにすべきでしょう。
実際に、あらゆるサービスのベースとなるイメージで、この命令形式が推奨されます。

大部分の他のケースでは、 
CMD はインタラクティブなシェル(bash、python、perl 等)で使われます。

たとえば、 CMD ["perl", "-de0"] 、 CMD ["python"] 、 CMD [“php”, “-a”] です。
この利用形式が意味するのは、 docker run -it python のように実行すると、それを使いやすいシェル上に落とし込み、すぐに使えるようにします。 
あなたとあなたの想定ユーザが ENTRYPOINT の動作になれていない限り、CMD を ENTRYPOINT と一緒に CMD [“パラメータ”, “パラメータ”] の形式で使うべきではないでしょう。

【もくもく例】

RUN gem update bundler
RUN bundle install --without mysql postgresql rmagick test
RUN bundle exec rake db:migrate
RUN bundle exec rake generate_secret_token
CMD bundle exec rails s -p 3000 -b '0.0.0.0'
EXPOSE 3000

EXPOSE: コンテナ内のプロセスがListenするポート

【解説】 Dockerfile リファレンスの EXPOSE 命令 → 本家

EXPOSE 命令は、コンテナが接続用にリッスンするポートを指定します。
そのため、アプリケーションが一般的に使う、あるいは、伝統的なポートを使うべきです。
例えば、Apache ウェブ・サーバのイメージは、 EXPOSE 80 を使い、
MongoDB を含むイメージは EXPOSE 27017 を使うでしょう。

外部からアクセスするためには、ユーザが docker run 実行時にフラグを指定し、
特定のポートを任意のポートに割り当てられます。
コンテナのリンク機能を使うと、Docker はコンテナがソースをたどれるよう、環境変数を提供します(例: MYSQL_PORT_3306_TCP )。
EXPOSE <port> [<port>...]
  • EXPOSE 命令は、特定のネットワーク・ポートをコンテナが実行時にリッスンすることを Docker に伝えます。
  • EXPOSE があっても、これだけではホストからコンテナにアクセスできるようにしません。
    アクセスするには、 -p フラグを使ってポートの公開範囲を指定するか、 -P フラグで全ての露出ポートを公開する必要があります。
    外部への公開時は他のポート番号も利用可能です。
  • ホストシステム上でポート転送を使うには、 -P フラグを使う をご覧ください。
  • Docker のネットワーク機能は、ネットワーク内でポートを公開しないネットワークを作成可能です。
    詳細な情報は 機能概要 をご覧ください。

Dockerfile コマンドについては、一旦ここまで。

Docker お勉強 02 各種dockerコマンドのまとめ

ここまでの

各種dockerコマンドのまとめ

その他のコマンドについては、公式ページ「 Docker Documentation – docker 」から確認できます。 docker | Docker Documentation

Dockerイメージ取得、イメージ一覧確認

# docker pull <Dockerイメージ名>

# docker images

※公開されているDockerイメージを管理しているところからダウンロードしたり、自分でDockerイメージを作成したりすることで初めて一覧に表示されます。

コンテナ起動、コンテナ一覧確認

※Dockerコンテナとは、Dockerイメージをもとに起動させたコンテナのことを指します。

# docker run -it -d -p <ホスト側ポート>:<コンテナ側ポート> -v <ホスト側ディレクトリ>:<コンテナ側ディレクトリ> --name <コンテナ名> <Dockerイメージ名>

# docker ps -a
※「-p」「-v」オプション以外にも多数のオプションがあります。

コンテナへのログイン

# docker exec -it <コンテナ名> bash

ファイルコピー

(ホスト→コンテナ内)
# docker cp <ホスト側のファイル> <コンテナ名>:<コンテナ内のコピー先ディレクトリ>

(コンテナ内→ホスト)
# docker cp <コンテナ名>:<コンテナ内のコピー元ファイル> <ホスト側のコピー先ディレクトリ>

コンテナからDockerイメージ作成

# docker commit <コンテナ名> <作成するDockerイメージ名>

コンテナ起動、停止

# docker start <コンテナ名>

# docker stop <コンテナ名>

コンテナ削除

# docker rm (-f) <コンテナ名>
※「-f」オプションを付けると、コンテナ起動中でも強制的に削除します。

Docker お勉強 01 起動・イメージ作成

Redmineプラグインのもくもくをやるのに、Dockerで開発環境を準備する事にしたけれど、使い方を全く知らないので少しづつお勉強します。

さくらのナレッジ の Docker入門でお勉強して、覚え書きをここへ記載します。

knowledge.sakura.ad.jp

公開されているDockerイメージを使用し、コンテナの基本的な操作

Dockerイメージ、コンテナのライフサイクル

  1. 公開されているDockerイメージの取得
  2. それをもとにコンテナを起動
  3. コンテナの停止・削除
  4. Dockerイメージの削除

公開されているDockerイメージの取得

「hello-world」というDockerイメージを使用して、コンテナを起動。

 # docker run hello-world

このhello-worldイメージは、コンテナ起動をしたら、コンテナの中でメッセージを出力するようなDockerイメージとなっています。

docker run

Dockerイメージを使用して、コンテナを起動するコマンド

※最初はローカル環境にはDockerイメージはありません。そのため、Docker Hubに探しに行き、存在すればDockerイメージをダウンロードします。

コンテナの状態
# docker ps -a
CONTAINER ID   IMAGE         COMMAND    CREATED          
c1b9013f9f66   hello-world   "/hello"   27 minutes ago 
  
STATUS                      PORTS   NAMES
Exited (0) 27 minutes ago           distracted_swirles
CONTAINER ID コンテナのID 備考
IMAGE 使用したDockerイメージ
COMMAND コンテナ起動時にコンテナ内で実行するコマンド
CREATED コンテナを作成したタイミング
STATUS コンテナ状態 Exited:停止中
Up: 起動中
PORTS ポートフォワード設定
NAMES コンテナの名前

※コンテナの名前は、指定しなければ自動で設定されます。

(コンテナの名前の指定の仕方は後ほど)

※ コンテナは、1プロセスは動いている必要があります。

 今回のhello-worldイメージでは、メッセージを出力するだけのプロセスだったため、そのプロセスが終了したタイミングでコンテナは停止しています。

Dockerコンテナ、イメージを削除する

コンテナ一覧

# docker ps -a
CONTAINER ID   IMAGE         COMMAND    CREATED          
c1b9013f9f66   hello-world   "/hello"   27 minutes ago   

STATUS                      PORTS   NAMES
Exited (0) 27 minutes ago           distracted_swirles

※停止中のものをそのままにしておくと、ゴミデータが残ったままになったり、ディスク圧迫にもつながるので、不要になったものは削除を忘れないようにしましょう。

※「docker ps」(「-a」を付けない)と実行すると、停止中のコンテナは表示されないので、注意してください。

 気づいたらたくさんの不要コンテナが溜まっていた、ということにもなりかねません。

イメージ一覧

# docker images
REPOSITORY    TAG      IMAGE ID       CREATED        SIZE
hello-world   latest   f2a91732366c   3 months ago   1.85kB

※イメージについても、不要なイメージは削除しましょう。

 特に、JenkinsなどのCI(Continuous Integration) ツールを使用して、Dockerイメージの作成やコンテナ起動を自動で実行するような場合、気づかないうちに不要データが残り続けて、ディスクを圧迫することになるので注意しましょう。

 (Disk Fullを起こすと、Docker環境の復元も困難になる場合があるので、十分注意したほうが良いです。)

コンテナを削除
# docker rm <コンテナ名>
または
# docker rm <CONTAINER ID>

例:

docker rm distracted_swirles
docker rm c1b9013f9f66
Dockerイメージの削除

コンテナを削除しても、Dockerイメージまでは削除されません。

# docker rmi <Dockerイメージ名> 
または
# docker rmi <IMAGE ID> 

例:

# docker rmi hello-world
または
# docker rmi f2a91732366c

Nginxイメージを使ってコンテナを動かしてみる

NginxのDockerイメージを取得

# docker pull nginx

取得したNginxイメージを使用してコンテナを起動

Nginxコンテナが起動するので、8181ポートに外からアクセスできるように設定すれば(Firewallなど)、以下のURLでNginxにアクセスすることができます。

http://<作成したインスタンスIPアドレス>:8181/

# docker run -d --name nginx-container -p 8181:80 nginx

実行コマンドのオプション

オプション 説明
-d コンテナをバックグラウンド実行します。
このオプションを付けない場合、コンテナ起動時に実行されるコマンドを実行した状態になり、例えばそのコマンドのコンソール出力が表示された状態になります。
–name コンテナ名を指定します。
(指定しない場合、自動で名前がつく)
-p ホストとコンテナ間のポートフォワード設定。
基本的には、「-p <ホスト側のポート>:<コンテナ側のポート>」で書きます。
(ホスト側を省略すると自動で設定されます。)
コンテナは、Dockerにより作成されるネットワークに属するため、このオプションを使わないと、ホストのIPアドレスを用いて、コンテナで使用しているポートにはアクセスができません。

コンテナ状態を確認

Nginxイメージの場合は、コンテナ起動時にnginxプロセスが実行されたままの状態になるため、コンテナも停止せずに起動状態となります。

docker ps コマンドで見てみると、STATUSが「Up」となっています。

# docker ps -a
CONTAINER ID   IMAGE   COMMAND                  CREATED      
a61413193f44   nginx   "nginx -g 'daemon ..."   7 days ago   

STATUS         PORTS                  NAMES
Up 8 seconds   0.0.0.0:8181->80/tcp   nginx-container

コンテナを停止する

# docker stop nginx-container
nginx-container

Proxy配下でDockerを使用する場合

Docker CEの場合

# mkdir -p /etc/systemd/system/docker.service.d
# vi /etc/systemd/system/docker.service.d/http-proxy.conf

[Service]
Environment="HTTP_PROXY=http://<プロキシサーバのIPアドレス>:<プロキシサーバのポート>/ HTTPS_PROXY=http://<プロキシサーバのIPアドレス>:<プロキシサーバのポート>/" 

# systemctl daemon-reload
# systemctl restart docker

Docker for Mac/Windowsの場合 メニューからたどる

preferences > proxies

公開Dockerイメージにミドルウェアやアプリをインストールして利用する

CentOSのDockerイメージをもとにTomcatが動くコンテナを構築するなかで、各種dockerコマンドについて説明します。

また、自分で構築したコンテナから、Dockerイメージを作成し、使用します。

CentOSのDockerイメージを取得し、コンテナを起動する

(docker runの「-v」オプションについては、この後説明する)

[ホスト]

# docker pull centos:7
# mkdir -p /root/tomcat-container/logs
# docker run -it -d -p 18080:8080 -v /root/tomcat-container/logs:/share/logs --name tomcat centos:7
  • 「/root/tomcat-container/logs」は任意のディレクトリで良い。
  • mkdir -pオプション(--parentsオプション)
ディレクトリが存在していてもエラーメッセージを表示せず、
現在あるディレクトリはそのままにして新規に作成することはなく子ディレクトリを作成する。
CentOSコンテナの中でTomcatインストール

今回使用しているCentOSのDockerイメージでは、インストール済みのコマンドは限られています。

そのため、必要に応じてyumなどでインストールする必要があります。

まず、Tomcatをホスト側にダウンロード

コンテナの中でダウンロードも可能です。

今回は、ホスト側からコンテナへのファイルコピー方法を示すため、このような手順にしています。

[ホスト]

# wget http://www-eu.apache.org/dist/tomcat/tomcat-9/v9.0.6/bin/apache-tomcat-9.0.6.tar.gz
以下のコマンドでホスト側からコンテナ内にファイルをコピー

[ホスト] docker cp <ホスト側のファイル> <コンテナ名>:<コンテナ内のコピー先ディレクトリ>

# docker cp apache-tomcat-9.0.6.tar.gz tomcat:/opt/

また、コンテナからファイルを取得することも可能です。以下のようなコマンドになります。

docker cp <コンテナ名>:<コンテナ内のコピー元ファイル> <ホスト側のコピー先ディレクトリ>

コンテナにログイン

[ホスト]

# docker exec -it tomcat bash

docker exec [オプション] [コンテナ] [コマンド] [引数...]

オプション 説明
-d, --detach 指定したコマンドをコンテナ内でバックグラウンドで実行します。
-e, --env <定義> 指定したコマンドを実行する際に環境変数を適用します。
-i, --interactive 指定したコマンドを実行した後、コンテナの標準入力をDockerホストの標準入力と接続します。
-t, --tty コンテナ内で疑似的な仮想端末を割り当てて、Dockerホストの標準出力と接続します。
--privileged 指定したコマンドを特権モードで実行します。
-u, --user 指定したコマンドを特定ユーザで実行します。
-w, --workdir コンテナ内でコマンドを実行する際の作業ディレクトリが指定できます。

コンテナ内でTomcatのインストール

[コンテナ内]

# yum install -y java
# cd /opt/
# tar zxf apache-tomcat-9.0.6.tar.gz
# cd apache-tomcat-9.0.6
# ./bin/startup.sh

これでTomcatが起動しました。物理マシン上にインストールする場合と基本同じです。

ブラウザで以下にアクセスすれば、Tomcatの画面が開きます。

docker runコマンドの「-p」オプションでホスト側の18080ポートをコンテナ内の8080にポートフォワードするようにしているので、18080ポートを使用することになります。

http://:18080/

これでTomcatのコンテナができました。
自分でWebアプリケーションを作成していたら、Tomcat配下のwebappsディレクトリに配置すれば、コンテナ内で自分のアプリケーションを起動させることができます。

ホスト側とのディレクトリ共有

注意点

Tomcatのログはコンテナのディレクトリにあります。

そのため、コンテナが停止してしまったら、ログを取得するために再度コンテナを起動させる必要があります。

また、もしコンテナが削除されてしまった場合は、ログの取得すらできなくなってしまいます。

実際に運用しているサービスでログが取得できないのは大きな問題です。

解決する方法

ホスト側とコンテナのディレクトリを共有させる方法があります。

docker run「-v」オプションが、その設定になります。

コロンの左側(/root/tomcat-container/logs)がホスト側、右側(/share/logs)がコンテナ内のディレクトリを表していて、そのディレクトリが共有されるかたちとなります。

Tomcatログはデフォルトのディレクトリ(/opt/apache-tomcat-9.0.6/logs)に出力されているので、コンテナにログインして、ログ出力先を変えましょう。

[コンテナ内]

# cd /opt/apache-tomcat-9.0.6/
# sed -i -e "s/\${catalina.base}\/logs/\/share\/logs/g" ./conf/logging.properties
# ./bin/shutdown.sh
# ./bin/startup.sh
# ls -la /share/logs/

コンテナ内で/share/logs/配下にログが出力されていることが確認できます。

次にホスト側で確認します。

新しいコンソールを開くか、コンテナからログアウト(exit)し、ホスト側のディレクトリ(/root/tomcat-container/logs/)を確認してみてください。

[ホスト]

# ls -la /root/tomcat-container/logs/

これでコンテナが停止しても、ホスト側でログを確認することができます。

試しにコンテナを停止

[ホスト]

# docker stop tomcat
tomcat

これでコンテナが停止しました。

先ほど確認したホスト側のディレクトリ(/root/tomcat-container/logs/)を確認してみるとログが残っているはずです。

その他、ホスト側とコンテナ内でディレクトリ共有する目的の例としては、以下のようなケースがあります。

・設定ファイルをホスト側においておき、その設定ファイルをコンテナ内で使用する。
・ソースコードがあるディレクトリを共有させて、コンテナ内でビルドやユニットテストを実行できるようにする。

コンテナからDockerイメージを作成し、使用する。

ここで作成したコンテナを削除してしまうと、もう一度同じコンテナを使いたいとなった場合に、再度同じ手順を踏む必要があります。

(また、同じものを増やしたい場合に、再度Tomcatインストールから行うのは手間です)

Dockerイメージにすることで、そのイメージを使ってコンテナ起動すれば、既に各種ミドルウェアなどがインストールされた状態のコンテナを起動することができます。

Dockerイメージを作成する方法は、以下の方法があります。

  1. Dockerfileを使用してDockerイメージを作成する
  2. コンテナからDockerイメージを作成する
Dockerfile

今回実施したインストールコマンドなどを書いたファイル。

手順がそのままコード化された状態となり、再現性も担保できるようになります。

具体的には以下のような内容になります。

FROM centos:7
RUN yum install -y java
CMD ["/bin/bash"]
コンテナからDockerイメージを作成
  • 現状のコンテナ状態をDockerイメージにするコマンドがあります。
    このやり方だと、そのDockerイメージは何の操作をしてできたものなのかもわからない状態になってしまいます。
  • Dockerイメージの作成は、基本的には 1.のDockerfileを使用したやり方がよい。
    (Docker Hubで公開されているDockerイメージも、それぞれDockerfileが公開されていて、それを元に作成されたイメージです。)
    暫定的にDockerイメージにしておきたい場合などに行うくらいが良い。
まず、コンテナを停止

[ホスト]

# docker stop tomcat
コンテナ名を指定し、Dockerイメージを作成

[ホスト]

docker commit <コンテナ名> <作成するDockerイメージ名>

# docker commit tomcat tomcat-image
新しくDockerイメージができたことを確認

[ホスト]

# docker images

作成したDockerイメージを使用してコンテナ起動

[ホスト]

コマンドの一番後ろで、作成したDockerイメージの「tomcat-image」を指定しています。

ホスト側のポートと、ホスト側の共有ディレクトリについては、最初に起動したコンテナとは被らないようにしましょう。

# mkdir -p /root/tomcat-container/logs2
# docker run -it -d -p 18082:8080 -v /root/tomcat-container/logs2:/share/logs --name tomcat2 tomcat-image

コンテナ状態を確認してみます。

[ホスト]

tomcat2という名前のコンテナが起動しています。

また、IMAGEのところを見ると「centos:7」ではなく、「tomcat-image」になっています。

# docker ps -a
CONTAINER ID   IMAGE          COMMAND       CREATED             
72472672e66b   tomcat-image   "/bin/bash"   5 seconds ago       
4ba6f6f55040   centos:7       "/bin/bash"   About an hour ago   

STATUS                            PORTS                     NAMES
Up 2 seconds                      0.0.0.0:18082->8080/tcp   tomcat2
Exited (137) About an hour ago                              tomcat

実際にコンテナの中を確認し、Tomcatも起動してみます。

[ホスト]

# docker exec -it tomcat2 bash

[コンテナ内]

# ls /opt
apache-tomcat-9.0.6 apache-tomcat-9.0.6.tar.gz
(Tomcatディレクトリが確かに存在します)
# cd /opt/apache-tomcat-9.0.6
# ./bin/startup.sh

ブラウザから以下にアクセスするとTomcat画面が見れるはず。

(新しく起動したコンテナのホスト側のポートは18082にした)

http://:18082/

停止したコンテナは、以下のコマンドで再度起動することができます。

18080ポートでアクセスすれば、最初に起動したコンテナ側に接続できます。

[ホスト]

# docker start tomcat
# docker ps -a
CONTAINER ID   IMAGE          COMMAND       CREATED          
72472672e66b   tomcat-image   "/bin/bash"   45 seconds ago   
4ba6f6f55040   centos:7       "/bin/bash"   5 seconds ago    

STATUS          PORTS                     NAMES
Up 45 seconds   0.0.0.0:18082->8080/tcp   tomcat2
Up 5 seconds    0.0.0.0:18080->8080/tcp   tomcat