Skip to content

Instantly share code, notes, and snippets.

@yano3nora
Last active March 29, 2024 02:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yano3nora/55b12cb13cee9a6f9f5aefe4deb21995 to your computer and use it in GitHub Desktop.
Save yano3nora/55b12cb13cee9a6f9f5aefe4deb21995 to your computer and use it in GitHub Desktop.
[dev: Docker note] Virtual machine as container on linux. #docker #dev

Overview

www.docker.com
Docker Documantaition
Docker Hub
Docker Hub Official Repos
Dockerイメージの理解とコンテナのライフサイクル

64 bit アーキテクチャな Linux OS 上で動作する Go 言語製の Linux Container 構成ツール。「アプリケーションをプラットフォームに依存せず確実に動かす」ことや「開発者が手軽にアプリケーションの実行環境を手に入れる」ことにフォーカスしており、「インフラのプラットフォームを仮想化して提供する」ことを目的とした Virtual Box や VMWare などの VM (Virtual Machine) とは思想が異なる。

コンテナは Linux 上で動作する仮想実行環境 (要はプロセス) で VM に比べて低コストで作成・運用可能。Docker を使用すると アプリケーションは Linux OS 上のコンテナ内に「サービス」プロセスとして常駐する ことになる。

各インフラプラットフォームでも最近はこの「コンテナ実行環境」を提供しており、開発だけでなく本番環境でもガンガン利用されている。

Tips

Updates

Troubleshooting

Practice


Usage

Resources

Limit a container's resources - docs.docker.com
デフォルトでは、コンテナはリソース制約を持たず、ホストのカーネルスケジューラが許す限り多くのリソースを使用できます。
Dockerコンテナで利用できるリソースや権限を制限する
仮想化技術を利用するメリットの1つに、柔軟にリソースを管理できる点がある。Dockerでは古くからコンテナに割り当てるCPUリソースやメモリ容量を指定する機能があったが、Docker 1.6以降では割り当てるCPUリソースやメモリをより詳細に指定できるようになった。また、Docker 1.10ではブロックI/Oに関する制限も設定できるようになっている。

# コンテナのリソース状況確認
$ docker stats

Dockerfile

Dockerfile は Docker コンテナのイメージ生成を自動化する ためのファイル。例えば ruby:2.5.3 のようなコンテナイメージは公式 ( DockerHub ) で提供されているが、開発者がそのイメージをベースに何らかのアプリケーションを実装しようと思った場合 Dockerfile でカスタムコンテナイメージを生成 して、そのイメージからコンテナを立ち上げるのが一般的な開発手順。

Dockerfile リファレンス

Dockerfile は一見バッチのようだが、実際は「どんなプラットフォームでも常に同一の実行環境を提供する」ことを目的としたコンテナのレシピであり、仕様が少し複雑。FROM で指定した元ネタに対して RUN による変更をコミットとして重ね、次回以降のビルドのためにキャッシュしたりしている。

FROM ubuntu                # ubuntu:latest をベースにしてイメージ作成
ENV LANG C.UTF-8           # POSIX から UTF-8 にロケール変更
RUN apt-get update         # e.g. ) 冪等性のない更新の結果をコミット
RUN apt-get install ruby   # e.g. ) ruby のインストール結果をコミット
WORKDIR /app               # RUN / COPY などの実行ディレクトリを変更
COPY hoge /app/hoge        # ファイルやディレクトリをコンテナへコピー
CMD  rails s               # プロセス実行時に必ず走るエントリーポイント

ロケールについて、日本語 ja_JP.UTF-8 は大体のベースイメージに存在しないので下手に指定するとエラーになる。また、デフォルトは POSIX なのでこれも UTF-8 にしておかないと怖い。コンテナ内の Linux ファイルシステムなどでマルチバイトをむき出しで利用しない限りは C.UTF-8 にしておくのが無難みたい ... 参考 - Dockerではコンテナのlocaleの再確認を

CMD のルール

CMD はコンテナ実行時に走らせるコマンドで必須となる。また、このとき実行するコマンドは フォアグランドで実行され続けるもの ( = バックグランドで動作・終了しないもの ) でなければコンテナが終了してしまうので注意するコト。

dockerのENTRYPOINTとCMDの書き方と使い分け、さらに併用
Dockerfileを極めて、Dockerマスターになろう!

Dockerではコマンドをフォアグラウンドで動かさないとコンテナが停止してしまいます。特に、 デーモンプログラム は、 デフォルトでバックグランド動作をするため 設定したものの 「起動しない/自動的に停止してしまう」 といった不具合に遭う原因となるので注意が必要です。

例えばnginxはデフォルトはデーモンとして動くので、(nginxの)daemon off設定を行い、 フォアグラウンドで動かす必要があります。

ENTRYPOINT /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf

Dockerfile の Linter

hadolint/hadolint

Docker の静的解析ツール、Haskell 製なのでインストールには Stack が必要。

# Install
$ git clone https://github.com/hadolint/hadolint
$ cd hadolint
$ stack install

# Lint
$ hadolint /path/to/Dockerfile

Commands

Docker コマンドメモ

# Pull docker image.
$ docker pull centos:7
$ docker images
> REPOSITORY   TAG      IMAGE ID      CREATED     VIRTUAL SIZE
> centos       7        5182e96772bf  2 days ago  200MB

# Run container at once.
$ docker run -it --rm centos:7 echo 'hello'
> hello

# Run with mounting host directory. - https://goo.gl/qe2B6m
$ docker run -v .:/home/root centos:7 /bin/bash

# Launch shell on container.
$ docker exec -it ${CONTAINER_NAME} bash

# List running containers.
$ docker ps
$ docker ps -a  # With stopping containers

# Get ID
$ docker ps | grep unicorn | awk '{print $1}'  # unicorn とかいう名前つきコンテナの ID 部分だけ取得

# Control containers.
$ docker start ${CONTAINER_NAME}
$ docker stop ${CONTAINER_NAME}
$ docker rm ${CONTAINER_NAME}
$ docker rm $(docker ps -aq)  # Remove all containers.
$ docker restart ${CONTAINER_NAME}

# Remove images.
$ docker image prune  # Remove all <none> images.
$ docker rmi ${IMAGE_ID}

# Check disk.
$ docker system df

# Prune build caches.
$ docker builder prune

# Prune all objects.
$ docker system prune

# Destroy all.
$ docker stop $(docker ps -a -q)
$ docker rm $(docker ps -a -q)
$ docker rmi $(docker images -q)
$ docker volume rm $(docker volume ls -qf dangling=true)
$ docker-machine restart

Docker イメージの save / load

docker save - docs.docker.com
Docker イメージをファイルでやり取りする

Docker のイメージは Docker Hub などのリポジトリホスティングサービスに保存するのが一般的だが、ファイルとしてエクスポート/インポートすることも可能。

# save
$ docker pull busybox
$ docker save busybox > busybox.tar

# load
$ docker load -i busybox.tar
$ docker run -t busybox /bin/echo "Hello World"

コンテナ内操作コマンド attach / exec

Dockerコンテナ内で操作 attachとexecの違い

# Attach STDIN & STDOUT process to container.
$ docker attach ${CONTAINER_NAME}
# exit するとコンテナ停止 + シェル終了
# コンテナを止めずにシェル終了するなら ctrl + q 

# Create /bin/bash process onto container.
$ docker exec -it ${CONTAINER_NAME} /bin/bash
# exit でも新しく生まれた /bin/bash プロセスが死ぬだけでコンテナは止まらず

docker run のオプション

$ docker run [option] [(user/)image] [command]

オプション 説明
-a, --attach 起動したコンテナのコンソール出力をホストのコンソールへ出力
-d, --detach コンテナをバックグラウンドで起動
-h, --hostname 起動したコンテナ内に設定されるホスト名を指定
-b, --bind ネットワークのバインド -b 0.0.0.0 で外部から参照可能に
-i, --interactive attach していなくても標準出力をオープン
-m, --memory メモリの使用量を制限 -m 200m で 200 MB
--name 管理用の名前を設定 --name 'my_server'
-p, --publish ポートフォワーディングを設定 -p コンテナ側ポート:ホスト側ポート
--rm コンテナの実行終了時にコンテナを削除
-t, --tty コンテナの標準出力を ttyattach ( -i と一緒に用いる )
-v, --volume マウントを設定 ( 主にホストの特定のディレクトリへ )
-w ワークディレクトリの指定 -w="/usr/local/docker"

docker run 実行時に一時的にマウントさせて依存パッケージ群を取り扱う

Docker 開発で、コンテナ内で依存を全て取り扱う ... 具体的にはコンテナ内で bundle installyarn install を実行するような開発環境では、当然ホスト側のエディタからゲスト側の Linter 実体などを参照できない。( もっと言えば vendor 配下なんかはファイル見ることもできん ) 。

このような場合に以下コマンドで ランコマンド実行時のみ一時的にホスト / ゲスト間で vendornode_modules をマウントさせ、結果ホスト側からコンテナ内でインストールした依存パッケージ群を参照する ような手法がとれる。

$ docker run --rm -v $PWD:/app -w /app $APP_IMAGE_NAME bundle install --path=vendor/bundle
$ docker run --rm -v $PWD:/app -w /app $APP_IMAGE_NAME yarn install

コンテナ $APP_IMAGE_NAME 内で bundle install などを行った結果生まれる ./vendor/bundle について -v で実行ディレクトリとゲスト側の WORKDIR をマウントしているためにホスト側の ./vendor/bundle にもモノが入る仕組み。

Named Volume のマウント

Use volumes - docs.docker.com
Docker for Windows で postgres コンテナの Volume マウントを安全にする

Docker コンテナで DB サーバを建てた場合、プロセスが死ぬと中のデータも当然消えてしまう。対処としてデータ領域を Docker ホストにマウントするのが一般的だが Windows だとコケる。Named Volume を切って逃がすのがよろしいみたい。

因みに DB のデータを volumes で外だししている場合に、パスワードを途中で変えたりすると接続情報が正しくても弾かれたりする。そのような場合は一度当該ボリュームを docker volume rm で削除して再度接続してみること。

railsをdockerで構築して、Host ‘172.18.0.5’ is not allowed to connect to this MySQL server

# docker run 時にマウント先を Named Volume へ指定
$ docker run -d -v database:/var/lib/postgresql/data postgres:9.6.3
# docker-compose.yml で DB データのマウント先を Named Volume へ設定
services:
  db:
    image: postgres:9.6.3
    volumes:
      - database:/var/lib/postgresql/data 
volumes:
  database:

volumes の実格納場所は Docker のインストール場所による。CentOS に yum で入れた場合は /var/lib/docker/volumes 配下に格納される。このため ストレージ設計の関係でデータ領域を別パーティションへ分けたい場合は docker のデータルートを変更する必要がある ( シンボリックリンクとかでもイケそうだけど、削除が上手く働かなかったりする ) 。

# 実際に Docker ホストのどこにあるのか確認 → 開発時にここを参照
#
$ docker volume ls
> DRIVER              VOLUME NAME
> local               database
$ docker inspect database
> {
>   CreatedAt": "2018-10-01T18:44:50Z",
>   "Driver": "local",
>   "Labels": {},
>   "Mountpoint": "/mnt/sda1/var/lib/docker/volumes/database/_data",
>   "Name": "database",
>   "Options": {},
>   "Scope": "local"
> }
# Docker のデータルートを変更したい場合
#
$ systemctl stop docker
$ vi /etc/docker/daemon.json
> {
>   "data-root": "/path/to/another-dir"
> }
$ mv /var/lib/docker /path/to/another-dir
$ systemctl start docker

Named Volume を使った依存パッケージファイル群の取り扱い

dockerで開発環境構築時にvendor,node_modulesなどのフォルダをどうやっていくか
DockerでのNodeアプリ構築で学んだこと > node_modulesのボリュームトリック

通常プロジェクトごとの vendornode_modules などバック/フロントの依存パッケージ群は、Docker 開発時は 普通にプロジェクトディレクトリをマウントしてコンテナ側で依存をインストールした場合、中身がコンテナ側から覗けない ことが面倒になる。

これを解決するためによく取られている手法が Volume Trick と呼ばれる手法。ホストからコンテナにマウントする際に消されたくないフォルダを Docker Volume としてマウントすることで、コンテナから依存パッケージが覗けるようになる。

# docker-compose.yml
services:
  web:
    build:
      context: ./laravel
    volumes:
      - ./laravel:/var/www
      # 
      # ↓ で依存パッケージフォルダを名前付き Volume でマウント
      #
      - node_modules:/var/www/node_modules
volumes:
  node_modules: # ここで名前付き Volume を定義
    driver: local  # これがない場合は /var/lib/docker/ 配下のディレクトリに勝手に領域が作られる

node_modules/ and Docker volume mount 問題と対策

上記手法でコンテナに入って依存を除くことはできるようになったが、このままでは ホスト OS から依存パッケージが参照できない 問題が残る ( 依存解決をすべてコンテナ内でやっている場合は ) 。これに関しては少しハックだが Docker コマンドで一時的にマウントポイントやワーキングディレクトリを指定しながら特定コマンドをコンテナで実行する ことで 特定コマンドにより生成されるファイル群だけホストにも持ってくるにょん みたいなことで解決できる気がする。

$ export IMAGE_NAME="node:10-alpine"    # docker ps や docker-compose.yml で確認
$ export INSTALL_COMMAND="npm install"  # yarn install とか bundle install とか
$ export WORK_DIR="/app"                # プロジェクトによっては /var/www とか

$ cd my-project-root
$ docker run --rm -v $PWD:$WORK_DIR -w $WORK_DIR $IMAGE_NAME $INSTALL_COMMAND

Docker Compose

docker-composeを使うと複数コンテナの管理が便利に

1 つのサービスを実現するために、言語処理系 / DB サーバ / Web サーバ / アプリケーションなど、それぞれのプロセス ( コンテナ ) を Docker で立ち上げようとする場合、 docker run コマンドを組み合わせたバッチを組む必要がある。docker-compose は それら複数コンテナ設定を 1 つの .yml ファイルに記述しまとめて起動・管理することで 複数のコンテナを 1 つのサービスとして管理できるようにする ツール。複数コンテナを組み合わせる Docker 開発では必ずと言ってよいほど利用する。

Commands

# docker-compose.yml に定義されたサービス ( 複数コンテナ ) を操作
$ docker-compose pull              # ベースイメージをまとめて docker pull
$ docker-compose build             # 全てのサービスをまとめて docker build
$ docker-compose build --no-cache  # キャッシュを無効化

$ docker-compose ps      # 全サービスプロセス出力
$ docker-compose logs -f # 全サービスログ出力 ( -f が tail オプション )
$ docker-compose stop    # 全サービス停止
$ docker-compose rm      # 全サービス削除 ( 停止してるやつだけ ) 
$ docker-compose down    # 全サービス停止 → 削除

# サービスのワンオフ実行 ( イメージの変更としてコミットされない )
$ docker-compose run --rm ${SERVICE} ${COMMAND}          # pull 前なら pull も
$ docker-compose run --rm ${SERVICE} ${COMMAND} --build  # run して build 

# サービスの開始 / 再起動 ( .yml > services > service > command を自動実行 )
$ docker-compose up       # アタッチドモード ( コンソール占領 ) Ctrl+C で終了
$ docker-compose up -d    # デタッチドモード ( バックグラウンド実行 ) 
$ docker-compose restart  # .yml や .env 変更などは反映されません

# サービス ( コンテナ ) へ /bin/bash で入る
$ docker-compose exec ${SERVICE} /bin/bash  # bash だけでもいける

docker-compose.yml

  • v2 はローカルでの構成用で extendsmem_limit サポートしている
    • 2020.12 時点で最新は 2.4
  • v3 は Docker Engine (Swarm) での構成に互換性を持ち本番でのクラスター構成を想定している
    • 2020.12 時点で最新は 3.8
    • オーケストレーション前提なので mem_limit などのオプションは --compatibility フラグをつけないと使えない
  • version: '3' などの指定は version: '3.0' と同義なので注意
  • ローカル開発環境での利用は 2.x を選んどけばよさそう
# docker-compose.yml のサンプル
# db:  MySQL や PostgreSQL を入れる想定
# app: Rails とかを入れる想定
# web: Apache や nginx を入れる想定

version: '2.3'          # Compose file バージョン指定
volumes:                # 使用する Named Volume を指定
  database:             # DB データ領域をホストへマウントして永続化
services:               # 構成サービスを列挙
  db:                   # サービス名
    dns: 8.8.8.8        # DNS の指定
    image: postgres     # build 時ベースイメージ
    environment:        # 環境変数 ( ビルド後の run 時に参照可能に )
      - LANG=C.UTF-8    # 値を固定してコンテナへ引き渡し
      - TZ              # ホスト環境変数を引継ぎコンテナへ
    volumes:
      - database:/var/lib/postgresql/data
  app: 
    build: .            # どこの Dockerfile でビルドするか指定
    env_file: .env      # ビルド後 run 時反映 & environment 併用不可
    volumes:            # コンテナからホストへマウントするボリューム
      - .:/app
      - /app/.venv         # マウントから除外する例 (厳密には空領域を割り当ててる)
      - /app/node_modules  # ref. https://bit.ly/2MJLM8T
    depends_on:         # 依存サービス / build 順序設定
      - db 
  web: 
    build:              # ビルド詳細設定
      context: .        # ここの ↓ の Dockerfile 使え
      dockerfile: Dockerfile.web
      args:             # ビルド時に必要な環境変数のセット
        HOST: $HOST     # Dockerfile で ARG で使用宣言が必要
    ports:
      - "80:80"         # ポートフォワーディング
      - "443:443"       # ローカル開発で自己証明書使うとき用
    volumes_from:       # 他サービスのボリュームをマウント
      - app:rw          # :rw など権限設定可能
    depends_on:
      - app

Tips & References

Dockerfile に環境変数を渡す

docker-compose.yml > env_file, environment からコンテナに環境変数を渡せるが build 時の Dockerfile 内ではこの環境変数は使えない ことに注意。

ref. docker-composeのビルド中に環境変数が認識されない

docker-compose.yml > build > args に渡した環境変数を ARGexport したことにできるので、Dockerfile で環境変数渡したいなら ↓ のようにする。これ五千回くらい調べなおしてる。

  1. .env で定義
  2. docker-compose.yml > env_file: でロード
  3. build: > args:Dockerfile で読み取れるようにしてやる
  4. Dockerfile > ARG で呼び出す (= export する)

相対パス解決について

docker-compose のバージョンによっては Windows 環境だと相対パス . を解釈できずコケる場合がある。環境変数 $PWD を利用して回避するか、docker-compose 1.9.0 以上なら環境変数 COMPOSE_CONVERT_WINDOWS_PATHS1 にすることで回避。

環境変数と .env について

設定を環境変数に格納する - 12factor.net
Docker-Compose の変数定義について

Docker を用いた開発では「サーバに SSH で入って環境設定ファイルをいじって ... 」というような 実行環境への手作業の環境設定は基本行わない 。厳密には行えるが、Docker の「サービスをバイナリとしてビルド → デプロイ → ランタイム」という設計思想上とてもやりづらい。サービスは デプロイされるタイミングで「環境変数」によって振る舞いを変える べきであり ソースコードに認証情報や環境設定のべた書き をするべきでない。1 リポジトリ 1 サービスかつ、1 リポジトリ複数環境ということのよう。

docker-compose はデフォルトで同階層の .env ファイルやホストの export による環境変数をロードして docker-compose.yml 内で展開できる。これら環境変数は各サービスの environment 設定に「値なしで」指定することでコンテナへ引き渡すことができる。

$ vi .env
> VARIABLE=hoge
# docker-compose.yml
services:
  web: 
    command: env  # build 時に環境変数みせろ ( 意訳 )
    enviroment:   # .env で全サービス設定管理 → ここで明示がいいかな?
      - VARIABLE
$ docker-compose up
> Recreating web_1 ... done
> Attaching to web_1
web_1  | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
web_1  | HOSTNAME=59ca518d8971
web_1  | HOME=/root
web_1  | VARIABLE=hoge  # これらの値は Docker コンテナからも参照可能

コンテナから別コンテナを「コンテナ名」でホストとして参照する

how to reach another container from a dockerised nginx - Stack Overflow
docker+rails+puma+nginx+postgres (Production ready)

同一の Docker ホストでは、別コンテナをサービスを提供する「ホスト」としてコンテナ名で参照できる。システムのフロントに nginx などの WEB サーバを置いて、リクエストを各サービスにプロキシして捌かせたいときとかに便利。

# nginx.conf にて app コンテナの 3000 ポートへ飛ばす例

upstream app {
  server app:3000;
}

server {
  listen            80;
  server_name       www.example.com;
  keepalive_timeout 5;
  location ~ /\. {
    deny all;
  }
  location ~* ^.+\.(rb|log)$ {
    deny all;
  }
  location / {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    if (-f $request_filename) {
      break;
    }
    if (-f $request_filename.html) {
      rewrite (.*) $1/index.html break;
    }
    if (-f $request_filename.html) {
      rewrite (.*) $1.html break;
    }
    if (!-f $request_filename) {
      proxy_pass http://app;  # ファイルがない場合は app へ振り分け
      break;
    }
  }
  location ~* \.(ico|css|gif|jpe?g|png|js)(\?[0-9]+)?$ {
    expires max;
    break;
  }
}

envsubst で環境変数を nginx の設定ファイルへ動的にセット

Docker上のNginxのconfに環境変数(env)を渡すたったひとつの全く優れてない方法(修正:+優れている方法) echo、printfでパイプから受けた値を出力する

Docker for Mac など Docker Desktop でホスト OS へ飛ばすやつ

https://qiita.com/rock619/items/ca23123fa5a498d195ca


Examples

Nginx for Heroku

yano3nora/heroku.md > Nginx

LAMP - Apache 2.4 x PHP 7.2 x MySQL 5.7

PHP - hub.docker.com
MySQL - hub.docker.com
DockerでLAMP環境
docker-composeを使ってmy.cnfの共有は気を付けよう!
VirtualHostのServerNameに環境変数を入れる
Docker の php:apache で OpenSSL を有効にする

GitHub: yano3nora/lampdock

Rails 5 on Puma x Nginx x PostgreSQL

Quickstart: Compose and Rails
Nginx + Rails (Puma) on Docker のいくつかの実用パターン
Docker + Rails + Puma + Nginx + MySQL
Rails アプリケーションを Docker を使って開発する、ほとんどの人が通る(であろう)道
ポートを指定してPumaを起動するとsocketファイルをlistenしなくなる
VagrantとVirtualBoxでCentOSにnginxとpumaのRails環境を構築する
railsでpumaが走らない時に怪しむべきtmp以下

Nginx を別コンテナにして、ロードバランサ的に利用しつつ各サービスに振り分けるような複雑な構成をとる場合は VOLUME を使って static/public なディレクトリを nginx 用に別途マウントし、EXPOSE で Puma のポートを空けて Nginx からのぞけるようにしてあげればよい。ただし Heroku などのコンテナ環境では VOLUME や EXPOSE が使えなかったり、各コンテナが別 dyno 管理になるため微妙。このへんは展開するプラットフォームによる。

GitHub: yano3nora/railsdock

PHP x Nginx x MySQL for Laravel

Laravelの環境をDockerで構築するチュートリアル
PHP GD をインストールするための Dockerfile の少し複雑な内容メモ
Dockerのphpイメージでzip extensionを使えるようにする
mcrypt extension
mbstring extension

GitHub: yano3nora/lempdock

Django on uWSGI x Nginx x PostgreSQL

Docker Compose と Django - docs.docker.jp
Docker-Compose x Python3.6 + Nginx + MariaDB10 + uWSGI + Django - qiita.com

GitHub: yano3nora/djangdock

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment