20200217のdockerに関する記事は16件です。

Docker on WSL

Windows10のWSL上にDockerをインストールしたメモ

ソフトウエア環境

Windows 10 Home Ver. 1809 (build 17763.1039)
Ubuntu 18.04.02 LTS (on Windows subsystem for Linux)

手順

WSLとUbuntuのインストール

割愛

Dockerのインストール

※以下の作業はWSLを管理者権限で実行すること
(net stop LxssManagerでサービス停止後に管理者権限で起動すると確実?)

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository \
 "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
 $(lsb_release -cs) \
 stable"
$ sudo apt-get update
$ sudo apt-get install docker-ce=18.06.1~ce~3-0~ubuntu
$ sudo usermod -aG docker $USER

aptでインストールするパッケージにdocker.ioを選択しても動作している人もいるみたいですが、うちの環境ではダメでした。
docker-cdでも最新バージョンではうまく動かず18.06.1で動作確認できました。

この後WSLを一度再起動してから下記のようにDockerデーモンを起動します。

$ sudo cgroupfs-mount
$ sudo service docker start
$ docker version
Client:
 Version:           18.06.1-ce
 API version:       1.38
 Go version:        go1.10.3
 Git commit:        e68fc7a
 Built:             Tue Aug 21 17:24:51 2018
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          18.06.1-ce
  API version:      1.38 (minimum version 1.12)
  Go version:       go1.10.3
  Git commit:       e68fc7a
  Built:            Tue Aug 21 17:23:15 2018
  OS/Arch:          linux/amd64
  Experimental:     false

WSLを管理者権限で動作していないと

Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

というエラーが表示され、デーモンが起動しません。
明示的に右クリックから管理者権限で起動してもたまにこのエラーが出ることがあります。
その時はPowerShellで下記のサービスを一旦落としてから起動するとうまくいきます。

PS C:\Windows\system32> net stop LxssManager
LxssManager サービスを停止中です.
LxssManager サービスは正常に停止されました。

また、Docker.ioパッケージでインストールした場合は
サービスの起動まではできるもののイメージをpullしようとすると

Error response from daemon: OCI runtime create failed: container_linux.go:346 (以下略)

というエラーが出て使えませんでした。

その後

個人的な環境づくりのメモです。

$ docker run -v /c/usr:/var/c/usr -it ubuntu:latest

// 以下はコンテナ内で
# apt-get update
# apt-get install language-pack-ja git

参考

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

Dockerで起動したJupyterLabでvimキーバインドを使う

概要

前回Jupyter Notebookでvimのキーバインドが使えるようにしましたが、
JupyterLabの存在を完全に忘れていたので、、同様にDockerでJupyterLabを起動してvimのキーバインドが使えるようなやり方をまとめました。

参考:Dockerで起動したJupyter Notebookでvimキーバインドを使う

Jupyter NotebookとJupyterLabの違い

Jupyter notebookの後継機がJupyterLabになってまして、基本的に出来ることはどちらでも同じですが、JupyterLabの方が高機能になっています。
Jupyter notebookの開発は一旦終了して今後はJupyterLabに置き換わるようです。

環境

バージョン
Mac 10.15.3
Docker 19.03.4
docker-compose 1.24.1

環境構築手順

Dockerfileとdocker-composeを使ってvimのキーバインドが使えるJupyterLabを起動します。
※以下ファイルはGitHubにもまとめているのでご参考ください。
https://github.com/hikarut/Data-Science

1. notebook保存用のディレクトリ作成
$ mkdir notebooks
2. Dockerfileの作成
Dockerfile
FROM jupyter/datascience-notebook
USER root

RUN pip install jupyterlab==1.0
RUN jupyter serverextension enable --py jupyterlab
RUN jupyter labextension install jupyterlab_vim

EXPOSE 10000
CMD ["bash"]

jupyterlab_vimjupyterlab 1.0にしか対応してないようなのでバージョンを指定してインストールしてます
jupyterlab-vim:https://github.com/jwkvam/jupyterlab-vim

3. docker-compose.ymlの作成
docker-compose.yml
version: '3'
services:
  data-science:
    restart: always
    build: .
    container_name: 'data-science'
    ports:
      - "10000:10000"
    working_dir: '/root/'
    tty: true
    volumes:
        - ./notebooks:/root/notebooks/
4. コンテナのビルド
$ docker-compose up -d --build
5. コンテナにログイン
$ docker-compose exec data-science bash
6. Jupyter Notebookの起動
/root# jupyter lab --port 10000 --allow-root

表示されるhttp://127.0.0.1:10000/?token=xxxxxxxxxxxxxxxxにアクセスします。

Jupyter Notebookの時のように設定変更する必要もなく、そのままの状態でnotebook上でvimが使えるようになります
スクリーンショット 2020-02-17 22.34.20.png

最後に

JupyterLabの方がJupyter Notebookよりも気持ちセットアップが楽でした!
(今回もユーザーを全てrootにしちゃったのでその辺は変えた方が良いかもです。。。)
ただjupyterlab-vimのビルドがめちゃくちゃ遅いのが気になりましたが、、今後改善されると嬉しいなと思います。

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

PostgresのDockerImageに変更があってログインができなくなった話

はじめに

DockerでLocalの開発環境としてPostgresの環境を構築していたんですが、
急に動かなくなってしまったのでメモがわりに残しておきます。

環境

$ docker version

Client: Docker Engine - Community
 Version:           19.03.5
 API version:       1.40
 Go version:        go1.12.12
 Git commit:        633a0ea
 Built:             Wed Nov 13 07:22:34 2019
 OS/Arch:           darwin/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.5
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.12
  Git commit:       633a0ea
  Built:            Wed Nov 13 07:29:19 2019
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v1.2.10
  GitCommit:        b34a5c8af56e510852c35414db4c1f4fa6172339
 runc:
  Version:          1.0.0-rc8+dev
  GitCommit:        3e425f80a8c931f88e6d94a8c831b9d5aa481657
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

問題発生

docker-composeでPostgresのimageを設定してLocalで開発環境を構築していました。
ところが今日(2020/02/17)起動した際に急に動かなくなる自体が発生。

docker-compose.yml(抜粋)
services:
  db:
    image: postgres

docker-compose upで起動した際に以下のエラーが出ました。

Error: Database is uninitialized and superuser password is not specified.
       You must specify POSTGRES_PASSWORD for the superuser. Use
       "-e POSTGRES_PASSWORD=password" to set it in "docker run".

       You may also use POSTGRES_HOST_AUTH_METHOD=trust to allow all connections
       without a password. This is *not* recommended. See PostgreSQL
       documentation about "trust":
       https://www.postgresql.org/docs/current/auth-trust.html

調査

エラーの内容をみる限りはスーパーユーザーのパスワードが指定されていなかったことに起因するもので、
対応すべき内容も書いてあります。
一応該当のエラーで検索してみると以下のページにたどり着きました。
ちょうどこのページも3日前にopenされた内容です。

https://github.com/docker-library/postgres/issues/681

どうやら以下のコミットが5日ほど前にマージされた影響のよう。

該当のコミット

https://github.com/docker-library/postgres/commit/42ce7437ee68150ee29f5272428aa4fc657dc6dc#diff-ba37482f8f538bee02287ee127ffe99fR109

マージされたPR

https://github.com/docker-library/postgres/pull/658

今回は開発環境でパスワードが設定されていない状態でDBに接続していたので、
上記の改修で繋がらなくなってしまった模様。

解決

今回は自分のlocal環境で開発する際に確認のために一度だけ繋がればよかったので
POSTGRES_HOST_AUTH_METHODオプションをtrustにすることで対応しました。

本来はきちんとパスワードを設定した方が良いです。

docker-compose.yml(抜粋)
services:
  db:
    image: postgres
    environment:
      POSTGRES_HOST_AUTH_METHOD: 'trust'
      # POSTGRES_PASSWORD: 'postgres' ← envファイルに外出ししても良い

終わり

今回は

  • パスワードが設定されていなかった
  • ちょうど週末にimageを整理した際にpostgresのimageを一回削除していた

ことからこの状況が発生してしまいました。

開発環境であってもきちんとパスワードを設定しておく、imageのversionをロックしておくことは大事だと言う教訓ですね。
(ちなみにpostgresのバージョンをpostgres:9.5.10で指定した場合は発生せず)

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

PostgresのDockerImageに変更があって起動ができなくなった話

はじめに

DockerでLocalの開発環境としてPostgresの環境を構築していたんですが、
急に動かなくなってしまったのでメモがわりに残しておきます。

環境

$ docker version

Client: Docker Engine - Community
 Version:           19.03.5
 API version:       1.40
 Go version:        go1.12.12
 Git commit:        633a0ea
 Built:             Wed Nov 13 07:22:34 2019
 OS/Arch:           darwin/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.5
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.12
  Git commit:       633a0ea
  Built:            Wed Nov 13 07:29:19 2019
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v1.2.10
  GitCommit:        b34a5c8af56e510852c35414db4c1f4fa6172339
 runc:
  Version:          1.0.0-rc8+dev
  GitCommit:        3e425f80a8c931f88e6d94a8c831b9d5aa481657
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

問題発生

docker-composeでPostgresのimageを設定してLocalで開発環境を構築していました。
ところが今日(2020/02/17)起動した際に急に動かなくなる自体が発生。

docker-compose.yml(抜粋)
services:
  db:
    image: postgres

docker-compose upで起動した際に以下のエラーが出ました。

Error: Database is uninitialized and superuser password is not specified.
       You must specify POSTGRES_PASSWORD for the superuser. Use
       "-e POSTGRES_PASSWORD=password" to set it in "docker run".

       You may also use POSTGRES_HOST_AUTH_METHOD=trust to allow all connections
       without a password. This is *not* recommended. See PostgreSQL
       documentation about "trust":
       https://www.postgresql.org/docs/current/auth-trust.html

調査

エラーの内容をみる限りはスーパーユーザーのパスワードが指定されていなかったことに起因するもので、
対応すべき内容も書いてあります。
一応該当のエラーで検索してみると以下のページにたどり着きました。
ちょうどこのページも3日前にopenされた内容です。

https://github.com/docker-library/postgres/issues/681

どうやら以下のコミットが5日ほど前にマージされた影響のよう。

該当のコミット

https://github.com/docker-library/postgres/commit/42ce7437ee68150ee29f5272428aa4fc657dc6dc#diff-ba37482f8f538bee02287ee127ffe99fR109

マージされたPR

https://github.com/docker-library/postgres/pull/658

今回は開発環境でパスワードが設定されていない状態でDBに接続していたので、
上記の改修で繋がらなくなってしまった模様。

解決

今回は自分のlocal環境で開発する際に確認のために一度だけ繋がればよかったので
POSTGRES_HOST_AUTH_METHODオプションをtrustにすることで対応しました。

本来はきちんとパスワードを設定した方が良いです。

docker-compose.yml(抜粋)
services:
  db:
    image: postgres
    environment:
      POSTGRES_HOST_AUTH_METHOD: 'trust'
      # POSTGRES_PASSWORD: 'postgres' ← envファイルに外出ししても良い

終わり

今回は

  • パスワードが設定されていなかった
  • ちょうど週末にimageを整理した際にpostgresのimageを一回削除していた

ことからこの状況が発生してしまいました。

開発環境であってもきちんとパスワードを設定しておく、imageのversionをロックしておくことは大事だと言う教訓ですね。
(ちなみにpostgresのバージョンをpostgres:9.5.10で指定した場合は発生せず)

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

社内向け Kubernetes Hands On(第1回 Pod編)

Kubernetes Hands On(第1回)

Podの考え方

Podとは大まかに
Containerの集合体と考えることが出来ます。
簡単に考えると、
docker-composeで作成されるContainer群がPodだと考えることが可能です。

では、実際にPodを作成してみましょう。

pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: demo
  labels:
    name: demo
spec:
  containers:
    - name: demo
      image: ncsnozominishinohara/demo:v1
      resources: {}
      ports:
        - containerPort: 80

解説

apiVersion

kubernetesのバージョンによって使用できる値は変わります。
kubernetesの所属しているAPIGROUPによって書き方が異なります。
- 特定のApiGroupに属している場合・・・ apiVersion: ApiGroup/ApiVersion
- 特定のApiGroupに属していない場合(CoreGroupに属している)・・・ apiVersion: ApiVersion
対応しているApiGroupの調べ方は以下のコマンドを実行することで確認出来ます。(v1.17.2)

kubectl api-resource

kind

どのオブジェクトであるかを設定します。
今回であればkind: Podを指定しているため、
Podオブジェクトが作成されます。

metadata

このオブジェクトのメタ情報を設定します。

name: 任意の命名(後に他のkindオブジェクトで指定される値)
labels: 任意の命名が可能(但し制限もあるため、公式を参考に)

spec

オブジェクトの仕様を表します。

  • containers
    • name
      Pod名
    • image
      使用するコンテナイメージ名
    • resources
      CPU/メモリ等を設定
    • ports
      • containerPort
        コンテナ内からExposeされるポート番号

デプロイ方法

kubectl apply pod.yaml

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

Dockerの自動起動設定を外す

Docker 自動起動設定の解除

dockerの初期設定上、PC起動時に自動でdockerを起動する。
dockerはメモリを2~3GB食ってしまうので、必要のない時は起動しないように設定したいと思います。

手順

1. デスクトップのメニューバーにあるdockerアイコンを選択後、「Preferences」を選択する

スクリーンショット 2020-02-17 16.13.38.png

2. 「Start Docker Desktop when you log in」のチェックを外す

スクリーンショット 2020-02-17 16.26.10.png

3. 「Apply & Restart」を選択する

スクリーンショット 2020-02-17 16.27.31.png

4. 完了

以上、お疲れ様でした

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

DockerのBuild Cacheの削除

Dockerfileのbuildをしているとno space leftなるエラーが発生してしまいました。
docker system dfでDockerが使っているストレージ容量を確認したところBuild cacheがやたらに大きいことが判明。

$ docker system df
TYPE                TOTAL               ACTIVE              SIZE                RECLAIMABLE
Images              25                  6                   99.35GB             74.17GB (74%)
Containers          8                   6                   27.06GB             6.004GB (22%)
Local Volumes       3                   1                   0B                  0B
Build Cache         214                 0                   41.58GB             41.58GB

このBuild cahcheのクリア方法がすぐに分からなかったのでメモとして残しておきます。
結論としては非常に簡単で、次のコマンドで消せます(参考URL)。

$ docker builder prune

このコマンド実行後、docker system dfをしてみると...

$ docker system df
TYPE                TOTAL               ACTIVE              SIZE                RECLAIMABLE
Images              25                  6                   99.35GB             74.17GB (74%)
Containers          8                   6                   27.06GB             6.004GB (22%)
Local Volumes       3                   1                   0B                  0B
Build Cache         96                  0                   0B                  0B

無事に消せました。
Dockerを使っていてストレージの残り容量に困っている方は試してみる価値があるのではないでしょうか。

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

邪悪なDocker for Macを捨てて最速のDocker環境を手に入れる

Macでネイティブでの動作とほぼ同等の速度のDockerを手に入れることができたので、その知見について公開します。
ものによりますが、最大10倍程度パフォーマンスの向上が見られました。
※タイトルには若干誇張が含まれています

Docker for Macは遅い

MacでDockerを使って開発している方は体感していると思うのですが、Docker for Macの速度はネイティブで動かすのと比べて相当遅く、リソースも消費します。
自分はRails + React(webpack) というWebでは比較的一般的なスタックで開発しているのですが、Railsの初期読み込み・webpackの(差分)トランスパイル・yarn installなどが致命的に重く、LinuxでのDocker環境と比較してかなりDXが低下します。
特に大量のファイルの読み込みや、ファイル変更のリアルタイム検知が弱い印象が強いです。

また、Docker for Macは不安定な面もあり、(例えばgitのブランチ変更などで)大量のファイル変更が起こった場合にCPU使用率が100%に張り付いてしまい、再起動しないと戻ってこないなどの現象も起きることがあります。
com.docker.hyperkit 100% cpu usage is back again · Issue #3499 · docker/for-mac

この問題の大部分は調べた限りファイルシステムをマウントした際のオーバーヘッドに起因しており、docker-syncなどを使ってファイルシステムの扱い方を変えることである程度改善することなどが知られています。
しかし、docker-syncも不安定さが残るなど、懸念点があります。
参考: hanhan's blog - Docker for Macのmount遅い問題まとめ

また、自分は設定の煩雑さから使ったことがないのですが、Docker for MacはNFS Volume sharingに対応しているので、それを使うことでもある程度改善されるのではないかと思います。

この記事ではパフォーマンス低下の最も大きな原因となっているファイルシステムについていくつか改善案を試し、その中で最も効果があったVirtualBox(Ubuntu)を使い、ファイルシステムのマウントを行わずに(VMのネイティブ?ファイルシステム上で)Dockerを使う、という手法について解説しようと思います。

Docker for Macと今回構築する環境(VirtualBox + Docker)の違い

docker.png

基本的にはどちらもVM上でDockerが動作することになります。

Docker for Mac

Docker for Macでは、インストール時にAlphine LinuxベースのHyperkit VMがインストールされ、起動時に /var/run/docker.sock が生成されます。
このVMはDocker for Mac上で自動的に管理され、Mac上で docker, docker-compose コマンドを実行する際に特に設定をしていない限り docker.sock を経由して透過的にVM内でDockerコマンドが実行されるようになっています。

VirtualBox + Docker

一方、今回構築する環境ではVirtualBoxを使って実際にVMを管理することになります。
Vagrantを用いてVirtualBoxでUbuntu環境を構築し、その中でDockerを動作させます。
完全に独立した環境のため、ファイルシステムについては VMに設定しているものがDocker上にマウントされ 、Dockerコマンドについてはsshで接続した上でVM内から実行することになります。
VM内の docker.sock をMacにマウントすることでMac上から透過的にDockerコマンドを扱えるかもしれませんが、まだ検証していません。

ファイルシステムについてですが、Vagrantでファイルを同期する sync_folder 機能で指定できる同期タイプとして

  • VirtualBox(オプション指定なし)
  • NFS
  • rsync
  • SMB

があります。
しかし、今回はどれも使わずに(VM起動時に1度だけrsyncをする)、Vagrantから独立した手段でファイル同期を行います。

実際の速度差

Docker for Mac, VirtualBox + Docker, Macネイティブについて、計測が容易で差が大きかったものについて環境毎の実測値を記載します。

Command Docker for Mac VirtualBox + Docker Macネイティブ
yarn install 117.84s 117.84s 16.88s 6.8倍高速化
Rails起動後の初回アクセス
(curl)
22.43s 2.199s 3.32s 10倍高速化

VMの設定や使い方

VMの構築・設定

Vagrantを使ってVMを構築します。
実際に使っているVagrantfileの構成は以下です。
CPU・メモリ・ディスクサイズなどは適宜調整してください。

追加でプラグインとして

  • vagrant-disksize
  • vagrant-hostsupdater
  • vagrant-mutagen

が必要になるので、 $ vagrant plugin install vagrant-disksize vagrant-hostsupdater vagrant-mutagen を実行してプラグインをインストールしておいてください。

Vagrant.configure('2') do |config|
  config.vm.box = 'ubuntu/xenial64'

  config.vm.hostname = 'my-app'

  config.vm.network :private_network, ip: '192.168.50.10'

  config.vm.provider :virtualbox do |vb|
    vb.gui = false
    vb.cpus = 4
    vb.memory = 8192
    vb.customize ['modifyvm', :id, '--natdnsproxy1', 'off']
    vb.customize ['modifyvm', :id, '--natdnshostresolver1', 'off']
  end

  config.disksize.size = '30GB'
  config.mutagen.orchestrate = true

  config.vm.synced_folder './', '/home/vagrant/app', type: "rsync",
    rsync_auto: true,
    rsync__exclude: ['.git/', 'node_modules/', 'log/', 'tmp/']

  config.vm.provision 'shell', inline: <<-SHELL
    curl -fsSL https://get.docker.com -o get-docker.sh
    sh get-docker.sh
    usermod -aG docker vagrant

    curl -L "https://github.com/docker/compose/releases/download/1.25.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    chmod +x /usr/local/bin/docker-compose
  SHELL
end

VMの使い方

(実際のコマンドの実行はファイル同期の章に書いている内容を行った後にしてください。)
$ vagrant up を実行すると各設定が適用されたVMの作成・起動とrsyncの実行が行われ、 .//home/vagrant/app に同期されます。rsyncの実行は起動時のみで自動的な実行は行われません。
初回はprovisionでDockerとDocker Composeのインストールが行われます。

その後、 $ vagrant ssh をしてVMにSSHでログインし、 $ cd app && docker-compose up {rails} のようなコマンドを実行することでDockerコンテナが立ち上がります。
イメージのビルドなどの処理は必要に応じて適宜行ってください。
立ち上がったアプリケーションにはVagrantfileで指定しているIPでアクセスできます。 (http://192.168.50.10:{3000} など)

ファイル同期の手段

開発を行う際にはローカルとVM上でファイルを同期する必要がありますが、その際にVirtualBoxの標準の共有フォルダやNFSを使うと、VM内からDockerへマウントした際のパフォーマンスが結局大幅に低下します。
そのため、MacからVMへのファイルシステム自体のマウントは行わず、ファイル変更を検知して相互にファイルを転送するような同期手段が望ましいです。

Vagrantのrsyncでの同期がそれに近いのですが、その場合ホストからVMへ単方向の同期しか行われないという問題があります。
具体的にはVM内で $ bundle install$ yarn install を実行した際に更新される Gemfile.lockyarn.lock のようなファイルについてVMからMacへ同期が行われません。

VM内のファイル変更を検知してホストにrsyncを実行するようなコマンド実行も試したのですが、双方向にrsyncで適切な同期を行うというのは実現できませんでした。

ファイル同期のソリューション 「Mutagen

上記の問題についてですが、最終的にMutagenを使うことで解決しました。
VMの構築・設定の節で既に記載しているのですが、Mutagenをインストールし、 vagrant-mutagen プラグインを導入することで双方向のファイル同期が適切に行われます。
Mutagenのインストールですが、Macでは $ brew install mutagen-io/mutagen/mutagen を実行することで行う事ができます。

その後、Vagrantfileと同じ階層に以下の mutagen.yml を配置してください。

sync:
  app:
    mode: "two-way-resolved"
    alpha: "./"
    beta: "{my-app (Vagrantfileで指定したホスト名)}:/home/vagrant/app"
    ignore:
      vcs: true
      paths:
        - "/node_modules"
        - "/log"
        - "/tmp"

その後 $ vagrant up でVMを起動すると、Mutagenによって双方向のファイル同期が行われます。
この同期は双方向にほぼリアルタイムで行われ、ファイルシステムのマウントではなくファイルの転送で実現されているため、最終的にDockerコンテナへマウントされた際のオーバーヘッドはほぼ発生しません。

結果

結果として現在業務で行っている開発にVagrantとMutagenを導入したことで、Macでも快適にDockerを使った開発が行えるようになりました。
Docker for Macでは速度・リソース・安定性のどの面でも苦労していたのですが、現在はある程度安定したDocker環境を構築できています。
sshしなければ使えないなど多少不便な点も残ってはいるのですが、それを上回るメリットが享受できていると感じています。

Docker for Macでの開発に消耗している方は数多くいる印象なので、もし機会があればこの構成を試してみていただけると幸いです。

備考・注意点

実行環境のスペック

MacBook Pro (16-inch, 2019)
CPU: 2.4GHz 8コア Intel Core i9
メモリ: 64GB 2667MHz DDR4

Docker for MacへのMutagenの適用

MutagenはDocker for Macで扱うこともできるようです。
その際のパフォーマンスは計測できていないのですが、もしかしたらVagrantを経由した場合と同等の速度が出るかもしれません。

今回はMutagen導入時には既にVagrant + rsyncを使った場合の速度が非常に速いことが確認できていたので、設定がシンプルだったvagrant-mutagenを採用したのですが、Docker for Mac + Mutagenでの開発も調査する価値があるかと思います。

マイクロサービス開発への対応

場合によっては複数のリポジトリに横断するマイクロサービス開発をDockerのnetwork機能を用いて行っている場合があるかと思います。
この構成ではリポジトリ毎に完全に独立したVMが作成されるという特性上、Docker networkを使った開発が難しくなってしまう可能性があります。

現在マイクロサービスでの開発は行っていないため確認できていないのですが、解決策として

  • 上記Docker for MacへのMutagenの適用
  • 必要なマイクロサービスをsubtree(submodule)として配置したリポジトリを作り、そのリポジトリ上でVMを構築する

というものが使えないかと考えています。
これについては今後マイクロサービス開発が必要になったタイミングで検証を進めていこうかと考えています。

Mutagenの同期セッションについて

vagrant-mutagenでのファイル同期についてですが、 $ vagrant halt を行わずにVMが終了した場合にはセッションが削除されずに残ってしまうという問題が確認できています。
セッションが残っていた場合には次回のVM起動時にセッションが新たに作成され、多重にセッションが貼られたまま残り続けるという状態になってしまいます。
また、残っているセッションは mutagen.yml の設定を読み込まずに古い設定のまま動作し続けるためバグの原因となります(なりました)。

MacのクラッシュやVMの正常な終了が行えなかった際には $ mutagen sync list でセッションを確認し、不要なセッションについては $ mutagen sync terminate {session_id} で削除するようにしてください。

docker-composeの設定について

docker-compose.yml に以下のようにgem用、npm用のvolumeを作成しているのですが、設定に不備があってDocker for Macでパフォーマンスが落ちているなどあればコメントをいただけると助かります。

services:
  rails: &app_base
    volumes:
      - .:/usr/src/app:cached
      - bundle:/usr/local/bundle:cached
  frontend:
    volumes:
      - .:/usr/src/app:cached
      - npm:/usr/local/npm:cached

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

邪悪なDocker for Macを捨ててMac最速のDocker環境を手に入れる

Macでネイティブでの動作とほぼ同等の速度のDockerを手に入れることができたので、その知見について公開します。
ものによりますが、最大10倍程度パフォーマンスの向上が見られました。
※タイトルには若干誇張が含まれています

Docker for Macは遅い

MacでDockerを使って開発している方は体感していると思うのですが、Docker for Macの速度はネイティブで動かすのと比べて相当遅く、リソースも消費します。
自分はRails + React(webpack) というWebでは比較的一般的なスタックで開発しているのですが、Railsの初期読み込み・webpackの(差分)トランスパイル・yarn installなどが致命的に重く、LinuxでのDocker環境と比較してかなりDXが低下します。
特に大量のファイルの読み込みや、ファイル変更のリアルタイム検知が弱い印象が強いです。

また、Docker for Macは不安定な面もあり、(例えばgitのブランチ変更などで)大量のファイル変更が起こった場合にCPU使用率が100%に張り付いてしまい、再起動しないと戻ってこないなどの現象も起きることがあります。
com.docker.hyperkit 100% cpu usage is back again · Issue #3499 · docker/for-mac

この問題の大部分は調べた限りファイルシステムをマウントした際のオーバーヘッドに起因しており、docker-syncなどを使ってファイルシステムの扱い方を変えることである程度改善することなどが知られています。
しかし、docker-syncも不安定さが残るなど、懸念点があります。
参考: hanhan's blog - Docker for Macのmount遅い問題まとめ

また、自分は設定の煩雑さから使ったことがないのですが、Docker for MacはNFS Volume sharingに対応しているので、それを使うことでもある程度改善されるのではないかと思います。

この記事ではパフォーマンス低下の最も大きな原因となっているファイルシステムについていくつか改善案を試し、その中で最も効果があったVirtualBox(Ubuntu)を使い、ファイルシステムのマウントを行わずに(VMのネイティブ?ファイルシステム上で)Dockerを使う、という手法について解説しようと思います。

Docker for Macと今回構築する環境(VirtualBox + Docker)の違い

docker.png

基本的にはどちらもVM上でDockerが動作することになります。

Docker for Mac

Docker for Macでは、インストール時にAlphine LinuxベースのHyperkit VMがインストールされ、起動時に /var/run/docker.sock が生成されます。
このVMはDocker for Mac上で自動的に管理され、Mac上で docker, docker-compose コマンドを実行する際に特に設定をしていない限り docker.sock を経由して透過的にVM内でDockerコマンドが実行されるようになっています。

VirtualBox + Docker

一方、今回構築する環境ではVirtualBoxを使って実際にVMを管理することになります。
Vagrantを用いてVirtualBoxでUbuntu環境を構築し、その中でDockerを動作させます。
完全に独立した環境のため、ファイルシステムについては VMに設定しているものがDocker上にマウントされ 、Dockerコマンドについてはsshで接続した上でVM内から実行することになります。
VM内の docker.sock をMacにマウントすることでMac上から透過的にDockerコマンドを扱えるかもしれませんが、まだ検証していません。

ファイルシステムについてですが、Vagrantでファイルを同期する sync_folder 機能で指定できる同期タイプとして

  • VirtualBox(オプション指定なし)
  • NFS
  • rsync
  • SMB

があります。
しかし、今回はどれも使わずに(VM起動時に1度だけrsyncをする)、Vagrantから独立した手段でファイル同期を行います。

実際の速度差

Docker for Mac, VirtualBox + Docker, Macネイティブについて、計測が容易で差が大きかったものについて環境毎の実測値を記載します。

Command Docker for Mac VirtualBox + Docker Macネイティブ
yarn install 117.84s 16.86s 16.88s 6.8倍高速化
Rails起動後の初回アクセス
(curl)
22.43s 2.199s 3.32s 10倍高速化

VMの設定や使い方

VMの構築・設定

Vagrantを使ってVMを構築します。
実際に使っているVagrantfileの構成は以下です。
CPU・メモリ・ディスクサイズなどは適宜調整してください。

追加でプラグインとして

  • vagrant-disksize
  • vagrant-hostsupdater
  • vagrant-mutagen

が必要になるので、 $ vagrant plugin install vagrant-disksize vagrant-hostsupdater vagrant-mutagen を実行してプラグインをインストールしておいてください。

Vagrant.configure('2') do |config|
  config.vm.box = 'ubuntu/xenial64'

  config.vm.hostname = 'my-app'

  config.vm.network :private_network, ip: '192.168.50.10'

  config.vm.provider :virtualbox do |vb|
    vb.gui = false
    vb.cpus = 4
    vb.memory = 8192
    vb.customize ['modifyvm', :id, '--natdnsproxy1', 'off']
    vb.customize ['modifyvm', :id, '--natdnshostresolver1', 'off']
  end

  config.disksize.size = '30GB'
  config.mutagen.orchestrate = true

  config.vm.synced_folder './', '/home/vagrant/app', type: "rsync",
    rsync_auto: true,
    rsync__exclude: ['.git/', 'node_modules/', 'log/', 'tmp/']

  config.vm.provision 'shell', inline: <<-SHELL
    curl -fsSL https://get.docker.com -o get-docker.sh
    sh get-docker.sh
    usermod -aG docker vagrant

    curl -L "https://github.com/docker/compose/releases/download/1.25.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    chmod +x /usr/local/bin/docker-compose
  SHELL
end

VMの使い方

(実際のコマンドの実行はファイル同期の章に書いている内容を行った後にしてください。)
$ vagrant up を実行すると各設定が適用されたVMの作成・起動とrsyncの実行が行われ、 .//home/vagrant/app に同期されます。rsyncの実行は起動時のみで自動的な実行は行われません。
初回はprovisionでDockerとDocker Composeのインストールが行われます。

その後、 $ vagrant ssh をしてVMにSSHでログインし、 $ cd app && docker-compose up {rails} のようなコマンドを実行することでDockerコンテナが立ち上がります。
イメージのビルドなどの処理は必要に応じて適宜行ってください。
立ち上がったアプリケーションにはVagrantfileで指定しているIPでアクセスできます。 (http://192.168.50.10:{3000} など)

ファイル同期の手段

開発を行う際にはローカルとVM上でファイルを同期する必要がありますが、その際にVirtualBoxの標準の共有フォルダやNFSを使うと、VM内からDockerへマウントした際のパフォーマンスが結局大幅に低下します。
そのため、MacからVMへのファイルシステム自体のマウントは行わず、ファイル変更を検知して相互にファイルを転送するような同期手段が望ましいです。

Vagrantのrsyncでの同期がそれに近いのですが、その場合ホストからVMへ単方向の同期しか行われないという問題があります。
具体的にはVM内で $ bundle install$ yarn install を実行した際に更新される Gemfile.lockyarn.lock のようなファイルについてVMからMacへ同期が行われません。

VM内のファイル変更を検知してホストにrsyncを実行するようなコマンド実行も試したのですが、双方向にrsyncで適切な同期を行うというのは実現できませんでした。

ファイル同期のソリューション 「Mutagen

上記の問題についてですが、最終的にMutagenを使うことで解決しました。
VMの構築・設定の節で既に記載しているのですが、Mutagenをインストールし、 vagrant-mutagen プラグインを導入することで双方向のファイル同期が適切に行われます。
Mutagenのインストールですが、Macでは $ brew install mutagen-io/mutagen/mutagen を実行することで行う事ができます。

その後、Vagrantfileと同じ階層に以下の mutagen.yml を配置してください。

sync:
  app:
    mode: "two-way-resolved"
    alpha: "./"
    beta: "{my-app (Vagrantfileで指定したホスト名)}:/home/vagrant/app"
    ignore:
      vcs: true
      paths:
        - "/node_modules"
        - "/log"
        - "/tmp"

その後 $ vagrant up でVMを起動すると、Mutagenによって双方向のファイル同期が行われます。
この同期は双方向にほぼリアルタイムで行われ、ファイルシステムのマウントではなくファイルの転送で実現されているため、最終的にDockerコンテナへマウントされた際のオーバーヘッドはほぼ発生しません。

結果

結果として現在業務で行っている開発にVagrantとMutagenを導入したことで、Macでも快適にDockerを使った開発が行えるようになりました。
Docker for Macでは速度・リソース・安定性のどの面でも苦労していたのですが、現在はある程度安定したDocker環境を構築できています。
sshしなければ使えないなど多少不便な点も残ってはいるのですが、それを上回るメリットが享受できていると感じています。

Docker for Macでの開発に消耗している方は数多くいる印象なので、もし機会があればこの構成を試してみていただけると幸いです。

備考・注意点

実行環境のスペック

MacBook Pro (16-inch, 2019)
CPU: 2.4GHz 8コア Intel Core i9
メモリ: 64GB 2667MHz DDR4

Docker for MacへのMutagenの適用

MutagenはDocker for Macで扱うこともできるようです。
その際のパフォーマンスは計測できていないのですが、もしかしたらVagrantを経由した場合と同等の速度が出るかもしれません。

今回はMutagen導入時には既にVagrant + rsyncを使った場合の速度が非常に速いことが確認できていたので、設定がシンプルだったvagrant-mutagenを採用したのですが、Docker for Mac + Mutagenでの開発も調査する価値があるかと思います。

マイクロサービス開発への対応

場合によっては複数のリポジトリに横断するマイクロサービス開発をDockerのnetwork機能を用いて行っている場合があるかと思います。
この構成ではリポジトリ毎に完全に独立したVMが作成されるという特性上、Docker networkを使った開発が難しくなってしまう可能性があります。

現在マイクロサービスでの開発は行っていないため確認できていないのですが、解決策として

  • 上記Docker for MacへのMutagenの適用
  • 必要なマイクロサービスをsubtree(submodule)として配置したリポジトリを作り、そのリポジトリ上でVMを構築する

というものが使えないかと考えています。
これについては今後マイクロサービス開発が必要になったタイミングで検証を進めていこうかと考えています。

Mutagenの同期セッションについて

vagrant-mutagenでのファイル同期についてですが、 $ vagrant halt を行わずにVMが終了した場合にはセッションが削除されずに残ってしまうという問題が確認できています。
セッションが残っていた場合には次回のVM起動時にセッションが新たに作成され、多重にセッションが貼られたまま残り続けるという状態になってしまいます。
また、残っているセッションは mutagen.yml の設定を読み込まずに古い設定のまま動作し続けるためバグの原因となります(なりました)。

MacのクラッシュやVMの正常な終了が行えなかった際には $ mutagen sync list でセッションを確認し、不要なセッションについては $ mutagen sync terminate {session_id} で削除するようにしてください。

docker-composeの設定について

docker-compose.yml に以下のようにgem用、npm用のvolumeを作成しているのですが、設定に不備があってDocker for Macでパフォーマンスが落ちているなどあればコメントをいただけると助かります。

services:
  rails: &app_base
    volumes:
      - .:/usr/src/app:cached
      - bundle:/usr/local/bundle:cached
  frontend:
    volumes:
      - .:/usr/src/app:cached
      - npm:/usr/local/npm:cached

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

DXを大幅に低下させるDocker for Macを捨ててMac最速のDocker環境を手に入れる

※DXはデジタルトランスフォーメーションではなくてDeveloper Experienceの方です

Macでネイティブでの動作とほぼ同等の速度の安定したDocker環境を手に入れることができたので、その知見について公開します。
ものによりますが、最大10倍程度パフォーマンスの向上が見られました。

Docker for Macは遅い

MacでDockerを使って開発している方は体感していると思うのですが、Docker for Macの速度はネイティブで動かすのと比べて相当遅く、リソースも消費します。
自分はRails + React(webpack) というWebでは比較的一般的なスタックで開発しているのですが、Railsの初期読み込み・webpackの(差分)トランスパイル・yarn installなどが致命的に重く、LinuxでのDocker環境と比較してかなりDXが低下します。
特に大量のファイルの読み込みや、ファイル変更のリアルタイム検知が弱い印象が強いです。

また、Docker for Macは不安定な面もあり、(例えばgitのブランチ変更などで)大量のファイル変更が起こった場合にCPU使用率が100%に張り付いてしまい、再起動しないと戻ってこないなどの現象も起きることがあります。
com.docker.hyperkit 100% cpu usage is back again · Issue #3499 · docker/for-mac

この問題の大部分は調べた限りファイルシステムをマウントした際のオーバーヘッドに起因しており、docker-syncなどを使ってファイルシステムの扱い方を変えることである程度改善することが知られています。
しかし、docker-syncも不安定さが残るなど、懸念点があります。
参考: hanhan's blog - Docker for Macのmount遅い問題まとめ

また、自分は設定の煩雑さから使ったことがないのですが、Docker for MacはNFS Volume sharingに対応しているので、それを使うことでもある程度改善されるのではないかと思います。

この記事ではパフォーマンス低下の最も大きな原因となっているファイルシステムについていくつか改善案を試し、その中で最も効果があったVirtualBox(Ubuntu)を使い、ファイルシステムのマウントを行わずに(VMのネイティブ?ファイルシステム上で)Dockerを使う、という手法について解説しようと思います。

Docker for Macと今回構築する環境(VirtualBox + Docker)の違い

docker.png

基本的にはどちらもVM上でDockerが動作することになります。

Docker for Mac

Docker for Macでは、インストール時にAlpine LinuxベースのHyperkit VMがインストールされ、起動時に /var/run/docker.sock が生成されます。
このVMはDocker for Mac上で自動的に管理され、Mac上で docker, docker-compose コマンドを実行する際に特に設定をしていない限り docker.sock を経由して透過的にVM内でDockerコマンドが実行されるようになっています。

VirtualBox + Docker

一方、今回構築する環境ではVirtualBoxを使って実際にVMを管理することになります。
Vagrantを用いてVirtualBoxでUbuntu環境を構築し、その中でDockerを動作させます。
完全に独立した環境のため、ファイルシステムについては VMに設定しているものがDocker上にマウントされ 、Dockerコマンドについてはsshで接続した上でVM内から実行することになります。
VM内の docker.sock をMacにマウントすることでMac上から透過的にDockerコマンドを扱えるかもしれませんが、まだ検証していません。

ファイルシステムについてですが、Vagrantでファイルを同期する sync_folder 機能で指定できる同期タイプとして

  • VirtualBox(オプション指定なし)
  • NFS
  • rsync
  • SMB

があります。
しかし、今回はどれも使わずに(VM起動時に1度だけrsyncをする)、Vagrantから独立した手段でファイル同期を行います。

実際の速度差

Docker for Mac, VirtualBox + Docker, Macネイティブについて、計測が容易で差が大きかったものについて環境毎の実測値を記載します。
Railsへのアクセスについて、ネイティブよりVMの方が早い理由についてはよく分かっていません。

Command Docker for Mac VirtualBox + Docker Macネイティブ
yarn install 117.84s 16.86s 16.88s 6.8倍高速化
Rails起動後の初回アクセス
(curl)
22.43s 2.199s 3.32s 10倍高速化

VMの設定や使い方

VMの構築・設定

Vagrantを使ってVMを構築します。
実際に使っているVagrantfileの構成は以下です。
CPU・メモリ・ディスクサイズなどは適宜調整してください。

追加でプラグインとして

  • vagrant-disksize
  • vagrant-hostsupdater
  • vagrant-mutagen

が必要になるので、 $ vagrant plugin install vagrant-disksize vagrant-hostsupdater vagrant-mutagen を実行してプラグインをインストールしておいてください。

Vagrant.configure('2') do |config|
  config.vm.box = 'ubuntu/xenial64'

  config.vm.hostname = 'my-app'

  config.vm.network :private_network, ip: '192.168.50.10'

  config.vm.provider :virtualbox do |vb|
    vb.gui = false
    vb.cpus = 4
    vb.memory = 8192
    vb.customize ['modifyvm', :id, '--natdnsproxy1', 'off']
    vb.customize ['modifyvm', :id, '--natdnshostresolver1', 'off']
  end

  config.disksize.size = '30GB'
  config.mutagen.orchestrate = true

  config.vm.synced_folder './', '/home/vagrant/app', type: "rsync",
    rsync_auto: true,
    rsync__exclude: ['.git/', 'node_modules/', 'log/', 'tmp/']

  config.vm.provision 'shell', inline: <<-SHELL
    curl -fsSL https://get.docker.com -o get-docker.sh
    sh get-docker.sh
    usermod -aG docker vagrant

    curl -L "https://github.com/docker/compose/releases/download/1.25.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    chmod +x /usr/local/bin/docker-compose
  SHELL
end

VMの使い方

(実際のコマンドの実行はファイル同期の章に書いている内容を行った後にしてください。)
$ vagrant up を実行すると各設定が適用されたVMの作成・起動とrsyncの実行が行われ、 .//home/vagrant/app に同期されます。rsyncの実行は起動時のみで自動的な実行は行われません。
初回はprovisionでDockerとDocker Composeのインストールが行われます。

その後、 $ vagrant ssh をしてVMにSSHでログインし、 $ cd app && docker-compose up {rails} のようなコマンドを実行することでDockerコンテナが立ち上がります。
イメージのビルドなどの処理は必要に応じて適宜行ってください。
立ち上がったアプリケーションにはVagrantfileで指定しているIPでアクセスできます。 (http://192.168.50.10:{3000} など)

ファイル同期の手段

開発を行う際にはローカルとVM上でファイルを同期する必要がありますが、その際にVirtualBoxの標準の共有フォルダやNFSを使うと、VM内からDockerへマウントした際のパフォーマンスが結局大幅に低下します。
そのため、MacからVMへのファイルシステム自体のマウントは行わず、ファイル変更を検知して相互にファイルを転送するような同期手段が望ましいです。

Vagrantのrsyncでの同期がそれに近いのですが、その場合ホストからVMへ単方向の同期しか行われないという問題があります。
具体的にはVM内で $ bundle install$ yarn install を実行した際に更新される Gemfile.lockyarn.lock のようなファイルについてVMからMacへ同期が行われません。

VM内のファイル変更を検知してホストにrsyncを実行するようなコマンド実行も試したのですが、双方向にrsyncで適切な同期を行うというのは実現できませんでした。

ファイル同期のソリューション 「Mutagen

上記の問題についてですが、最終的にMutagenを使うことで解決しました。
今回Vagrantを採用している理由の一つとして、Vagrant PluginでVMに対してMutagenの設定が簡単に行える、というものもあります。
VMの構築・設定の節で既に記載しているのですが、Mutagenをインストールし、 vagrant-mutagen プラグインを導入することで双方向のファイル同期が適切に行われます。
Mutagenのインストールですが、Macでは $ brew install mutagen-io/mutagen/mutagen を実行することで行う事ができます。

その後、Vagrantfileと同じ階層に以下の mutagen.yml を配置してください。

sync:
  app:
    mode: "two-way-resolved"
    alpha: "./"
    beta: "{my-app (Vagrantfileで指定したホスト名)}:/home/vagrant/app"
    ignore:
      vcs: true
      paths:
        - "/node_modules"
        - "/log"
        - "/tmp"

その後 $ vagrant up でVMを起動すると、Mutagenによって双方向のファイル同期が行われます。
この同期は双方向にほぼリアルタイムで行われ、ファイルシステムのマウントではなくファイルの転送で実現されているため、最終的にDockerコンテナへマウントされた際のオーバーヘッドはほぼ発生しません。

結果

結果として現在業務で行っている開発にVagrantとMutagenを導入したことで、Macでも快適にDockerを使った開発が行えるようになりました。
Docker for Macでは速度・リソース・安定性のどの面でも苦労していたのですが、現在はある程度安定したDocker環境を構築できています。
sshしなければ使えないなど多少不便な点も残ってはいるのですが、それを上回るメリットが享受できていると感じています。

Docker for Macでの開発に消耗している方は数多くいる印象なので、もし機会があればこの構成を試してみていただけると幸いです。

備考・注意点

実行環境のスペック

MacBook Pro (16-inch, 2019)
CPU: 2.4GHz 8コア Intel Core i9
メモリ: 64GB 2667MHz DDR4

Docker for MacへのMutagenの適用

MutagenはDocker for Macで扱うこともできるようです。
その際のパフォーマンスは計測できていないのですが、もしかしたらVagrantを経由した場合と同等の速度が出るかもしれません。

今回はMutagen導入時には既にVagrant + rsyncを使った場合の速度が非常に速いことが確認できていたので、設定がシンプルだったvagrant-mutagenを採用したのですが、Docker for Mac + Mutagenでの開発も調査する価値があるかと思います。

マイクロサービス開発への対応

場合によっては複数のリポジトリに横断するマイクロサービス開発をDockerのnetwork機能を用いて行っている場合があるかと思います。
この構成ではリポジトリ毎に完全に独立したVMが作成されるという特性上、Docker networkを使った開発が難しくなってしまう可能性があります。

現在マイクロサービスでの開発は行っていないため確認できていないのですが、解決策として

  • 上記Docker for MacへのMutagenの適用
  • 必要なマイクロサービスをsubtree(submodule)として配置したリポジトリを作り、そのリポジトリ上でVMを構築する

というものが使えないかと考えています。
これについては今後マイクロサービス開発が必要になったタイミングで検証を進めていこうかと考えています。

Mutagenの同期セッションについて

vagrant-mutagenでのファイル同期についてですが、 $ vagrant halt を行わずにVMが終了した場合にはセッションが削除されずに残ってしまうという問題が確認できています。
セッションが残っていた場合には次回のVM起動時にセッションが新たに作成され、多重にセッションが貼られたまま残り続けるという状態になってしまいます。
また、残っているセッションは mutagen.yml の設定を読み込まずに古い設定のまま動作し続けるためバグの原因となります(なりました)。

MacのクラッシュやVMの正常な終了が行えなかった際には $ mutagen sync list でセッションを確認し、不要なセッションについては $ mutagen sync terminate {session_id} で削除するようにしてください。

docker-composeの設定について

docker-compose.yml に以下のようにgem用、npm用のvolumeを作成しているのですが、設定に不備があってDocker for Macでパフォーマンスが落ちているなどあればコメントをいただけると助かります。

services:
  rails:
    volumes:
      - .:/usr/src/app:cached
      - bundle:/usr/local/bundle:cached
  frontend:
    volumes:
      - .:/usr/src/app:cached
      - yarn:/usr/local/yarn:cached

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

コンテナ内でfluentd実行したら"unexpected error error_class=Errno::EADDRINUSE"でエラッた話

はじめに

書いた記事の続き?にあたるもの
https://qiita.com/arata-honda/items/03d6da7c9f84f67466a9

エラー内容

2020-02-17 11:21:26 +0900 [error]: #0 suppressed same stacktrace
2020-02-17 11:21:26 +0900 [info]: Worker 0 finished unexpectedly with status 1
2020-02-17 11:21:26 +0900 [info]: adding match in @mainstream pattern="docker.**" type="file"
2020-02-17 11:21:26 +0900 [warn]: #0 [output_docker1] 'time_format' specified without 'time_key', will be ignored
2020-02-17 11:21:26 +0900 [info]: adding match in @mainstream pattern="**" type="file"
2020-02-17 11:21:26 +0900 [warn]: #0 [output1] 'time_format' specified without 'time_key', will be ignored
2020-02-17 11:21:26 +0900 [info]: adding filter pattern="**" type="stdout"
2020-02-17 11:21:26 +0900 [info]: adding source type="forward"
2020-02-17 11:21:26 +0900 [warn]: #0 define <match fluent.**> to capture fluentd logs in top level is deprecated. Use <label @FLUENT_LOG> instead
2020-02-17 11:21:26 +0900 [info]: #0 starting fluentd worker pid=204 ppid=34 worker=0
2020-02-17 11:21:26 +0900 [info]: #0 [input1] listening port port=24224 bind="0.0.0.0"
2020-02-17 11:21:26 +0900 [error]: #0 unexpected error error_class=Errno::EADDRINUSE error="Address in use - bind(2) for 0.0.0.0:24224"
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/2.5.0/socket.rb:201:in `bind'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/2.5.0/socket.rb:201:in `listen'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/gems/2.5.0/gems/fluentd-1.9.2/lib/fluent/plugin_helper/server.rb:355:in `server_create_tcp_socket'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/gems/2.5.0/gems/fluentd-1.9.2/lib/fluent/plugin_helper/server.rb:212:in `server_create_for_tcp_connection'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/gems/2.5.0/gems/fluentd-1.9.2/lib/fluent/plugin_helper/server.rb:92:in `server_create_connection'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/gems/2.5.0/gems/fluentd-1.9.2/lib/fluent/plugin/in_forward.rb:172:in `start'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/gems/2.5.0/gems/fluentd-1.9.2/lib/fluent/root_agent.rb:200:in `block in start'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/gems/2.5.0/gems/fluentd-1.9.2/lib/fluent/root_agent.rb:189:in `block (2 levels) in lifecycle'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/gems/2.5.0/gems/fluentd-1.9.2/lib/fluent/root_agent.rb:188:in `each'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/gems/2.5.0/gems/fluentd-1.9.2/lib/fluent/root_agent.rb:188:in `block in lifecycle'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/gems/2.5.0/gems/fluentd-1.9.2/lib/fluent/root_agent.rb:175:in `each'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/gems/2.5.0/gems/fluentd-1.9.2/lib/fluent/root_agent.rb:175:in `lifecycle'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/gems/2.5.0/gems/fluentd-1.9.2/lib/fluent/root_agent.rb:199:in `start'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/gems/2.5.0/gems/fluentd-1.9.2/lib/fluent/engine.rb:248:in `start'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/gems/2.5.0/gems/fluentd-1.9.2/lib/fluent/engine.rb:147:in `run'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/gems/2.5.0/gems/fluentd-1.9.2/lib/fluent/supervisor.rb:592:in `block in run_worker'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/gems/2.5.0/gems/fluentd-1.9.2/lib/fluent/supervisor.rb:823:in `main_process'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/gems/2.5.0/gems/fluentd-1.9.2/lib/fluent/supervisor.rb:586:in `run_worker'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/gems/2.5.0/gems/fluentd-1.9.2/lib/fluent/command/fluentd.rb:338:in `<top (required)>'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/lib/ruby/gems/2.5.0/gems/fluentd-1.9.2/bin/fluentd:8:in `<top (required)>'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/bin/fluentd:23:in `load'
  2020-02-17 11:21:26 +0900 [error]: #0 /usr/bin/fluentd:23:in `<main>'
2020-02-17 11:21:26 +0900 [error]: #0 unexpected error error_class=Errno::EADDRINUSE error="Address in use - bind(2) for 0.0.0.0:24224"

エラー原因

ここら辺に近しい
psすると

/ # ps
PID   USER     TIME  COMMAND
    1 root      0:00 tini -- /bin/entrypoint.sh fluentd
    6 root      0:00 {fluentd} /usr/bin/ruby /usr/bin/fluentd -c /fluentd/etc/fluent.conf -p /fluentd/plugins
   20 root      0:00 /usr/bin/ruby -Eascii-8bit:ascii-8bit /usr/bin/fluentd -c /fluentd/etc/fluent.conf -p /fluentd/pl
   29 root      0:00 /bin/sh
  288 root      0:00 ps

すでにfluentdのプロセスがexecした時に起動されていてポート24224が使用されていたため

unexpected error error_class=Errno::EADDRINUSE error="Address in use - bind(2) for 0.0.0.0:24224"
のエラーがいっぱいでました。

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

[Docker] "Timed out waiting for the lifecycle-server to start"によりサービスが起動しない

Docker Desktopが2.2系にアップデートされて以降、表題のエラーが通知され起動しなくなった。

軽く調べたところ、docker/
for-win のIssue#4393
がヒットし、読み進めていくと、rezapci commented on 15 Oct 2019にて提示された方法でエラーは出なくなった。

しかし、次いで Error response from daemon: open \.\pipe\docker_engine_linux のエラーが表示される。

これは、表示されたエラーダイアログにて、Factory Defaultsのボタンを押して再起動することで、最終的には起動が確認できた。

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

Dockerのbridge接続の通信フローをnftablesで追う

はじめに

いまさらながらCentOS8でDockerを勉強しはじめました。で、外部ホスト<--->コンテナのパケットの流れを追ってみたくていろいろ調べました。

けど、リクエストは流れが追えたのですが、レスポンスが追えていません。。。
ひとまずリクエストの経路だけまとめます。

いろいろ前提条件

検証環境

以下の環境でトレースしています。

  • CentOS8(8.1.1911)
  • Docker(19.03.5)
  • firewalld(0.7.0)
  • iptables(1.8.2:nf_tables)
  • nftables(0.9.0)

Docker動作環境

userland proxy(docker-proxy)

iptables / nftables によるdockerネットワークの基本的な動作をみたかったので、docker-pxoryを利用していません。(ヘアピンNAT)
以下のファイルを配置して検証しています。

/etc/docker/daemon.json
{
    "userland-proxy": false
}

参考:https://github.com/nigelpoulton/docker/blob/master/docs/userguide/networking/default_network/binding.md

docker network and containers

検証環境として、以下のエントリで生成されたものを利用します。
CentOS8で構成したdockerホスト 10.254.10.252 、説明はradiusを対象にしていきます。

Docker Composeでネットワークサービス群を5分で作れるようにした(dhcp/radius/proxy/tftp/syslog)

Screenshot from Gyazo

以下のコンテナが生成されます。

server app address listen
proxy squid 172.20.0.2 8080/tcp
syslog rsyslog 172.20.0.3 514/udp
radius freeRADIUS 172.20.0.4 1812/udp
dhcp ISC-Kea 172.20.0.5 67/udp
tftp tftp-server - 69/udp
# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
b11308767849        infraserv:proxy     "/usr/sbin/init"    3 minutes ago       Up 3 minutes        0.0.0.0:8080->8080/tcp   proxy
33054f8b7d58        infraserv:tftp      "/usr/sbin/init"    35 hours ago        Up 2 hours                                   tftp
851ea861d04e        infraserv:syslog    "/usr/sbin/init"    35 hours ago        Up 2 hours          0.0.0.0:514->514/udp     syslog
dd3a657cfda2        infraserv:dhcp      "/usr/sbin/init"    35 hours ago        Up 2 hours          0.0.0.0:67->67/udp       dhcp
7249b9c4f11d        infraserv:radius    "/usr/sbin/init"    35 hours ago        Up 2 hours          0.0.0.0:1812->1812/udp   radius

以下のパラメータのネットワークが生成されます。

key value
name infraserv_infranet
subnet 172.20.0.0/24
interface docker1

tftpは --net=host な環境で動作しているため、 docker network は以下のような状態です。

# docker network inspect infraserv_infranet
[
    {
        "Name": "infraserv_infranet",
        "Id": "7ed8face2e4fec3110384fa3366512f8c78db6e10be6e7271b3d92452aefd254",
        "Created": "2020-02-15T05:37:59.248249755-05:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.20.0.0/24",
                    "Gateway": "172.20.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": true,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "7249b9c4f11de1f986892965671086d20957a6021269a5f5bc6dd85263bc0d70": {
                "Name": "radius",
                "EndpointID": "03ae6a9b9ff7817eea101955d2d6ff016982beb65c7dd6631c75c7299682c2dd",
                "MacAddress": "02:42:ac:14:00:04",
                "IPv4Address": "172.20.0.4/24",
                "IPv6Address": ""
            },
            "851ea861d04edeb5f5c2498cc60f58532c87a44592db1f6c51280a8ce27940bd": {
                "Name": "syslog",
                "EndpointID": "d18e466d27def913ac74b7555acc9ef79c88c62e62085b50172636546d2e72bb",
                "MacAddress": "02:42:ac:14:00:03",
                "IPv4Address": "172.20.0.3/24",
                "IPv6Address": ""
            },
            "b11308767849c7227fbde53234c1b1816859c8e871fcc98c4fcaacdf7818e89e": {
                "Name": "proxy",
                "EndpointID": "ffa6479b4f28c9c1d106970ffa43bd149461b4728b64290541643eb895a02892",
                "MacAddress": "02:42:ac:14:00:02",
                "IPv4Address": "172.20.0.2/24",
                "IPv6Address": ""
            },
            "dd3a657cfda211c08b7c5c2166f10d189986e4779f1dfea227b3afe284cbafec": {
                "Name": "dhcp",
                "EndpointID": "7371f4cf652d8b1bdbf2dc1e5e8ae97013a9a70b890c2caa36c2a7cc93b165df",
                "MacAddress": "02:42:ac:14:00:05",
                "IPv4Address": "172.20.0.5/24",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker1"
        },
        "Labels": {
            "com.docker.compose.network": "infranet",
            "com.docker.compose.project": "infraserv",
            "com.docker.compose.version": "1.25.3"
        }
    }
]

アドレスファミリ

説明を簡略するために、IPv4に絞っています。

Dockerでのパケットの流れについて

通信を追ってみる(radiusの場合)

今回は外部端末(10.254.10.105)から、Dockerホスト(10.254.10.252)あてにradiusのRequestを送付することを例にします。
自ホストに着信した後に転送されるので、注目するchainのhookは prerouting --> forward --> postrouting となります。
そのため、chainのtypeは、filterとnatだけ、に絞って説明します。

ルールは nft list ruleset から 不要なものを除外してますが、あまり有用な情報でもないので、補足にまとめました。

外部端末からのリクエスト(prerouting)

nft list ruleset からhookがpreroutingのものを抽出すると、以下となります。

         table ip nat {
           chain PREROUTING {
(1)          type nat hook prerouting priority -100; policy accept;
(2)->        fib daddr type local COUNTER jump DOCKER
           }
   ->(2)   chain DOCKER {
      ↓      meta l4proto udp udp dport 514 COUNTER dnat to 172.20.0.3:514
      ↓      meta l4proto udp udp dport 67 COUNTER dnat to 172.20.0.5:67
      ↓      meta l4proto tcp tcp dport 8080 COUNTER dnat to 172.20.0.2:8080
     (3)     meta l4proto udp udp dport 1812 COUNTER dnat to 172.20.0.4:1812
           }
         }

現時点での通信は 10.254.10.105:random --> 10.254.10.252:1812 となります。
(1) preroutingをhookしてnatを行うPREROUTINGというchainが選択される
(2) DstAddrはlocalなので、DOCKERというchainに飛ぶ
  addr type localは自ホスト(この場合はDockerホスト)が持つアドレスのことで、
  今回ならlo:127.0.0.1 ens192:10.254.10.252 docker1:172.20.0.1 のことです。
(3) DstPortは1812なので、DstAddrを172.20.0.4:1812にDNATする
引き続きの処理がないため、policy適用 -> accept

この時点の通信は 10.254.10.105:random --> 172.20.0.4:1812 となります。
宛先が172.20.0.4に変更されたため、 routing decision により forward の hook へ進むことになります。

外部端末からのリクエスト(forward)

nft list ruleset からhookがforwardのものを抽出すると、以下となります。

                                table ip filter {
                                  chain FORWARD {
(1)                                 type filter hook forward priority 0; policy drop;
(2)->                               COUNTER jump DOCKER-USER
        ->(3)(4)->                  COUNTER jump DOCKER-ISOLATION-STAGE-1
                    ->(5)           oifname "docker1" ct state related,established COUNTER accept
                      (6)->         oifname "docker1" COUNTER jump DOCKER
                                    iifname "docker1" oifname != "docker1" COUNTER accept
                                    iifname "docker1" oifname "docker1" COUNTER accept
                                  }
               ->(4)              chain DOCKER-ISOLATION-STAGE-1 {
                 (5)->              COUNTER return
                                  }
   ->(2)                          chain DOCKER-USER {
     (3)->                          COUNTER return
                                  }
                         ->(6)    chain DOCKER {
                            ↓       iifname != "docker1" oifname "docker1" meta l4proto udp ip daddr 172.20.0.3 udp dport 514 COUNTER accept
                            ↓       iifname != "docker1" oifname "docker1" meta l4proto udp ip daddr 172.20.0.5 udp dport 67 COUNTER accept
                            ↓       iifname != "docker1" oifname "docker1" meta l4proto tcp ip daddr 172.20.0.2 tcp dport 8080 COUNTER accept
                           (7)      iifname != "docker1" oifname "docker1" meta l4proto udp ip daddr 172.20.0.4 udp dport 1812 COUNTER accept
                                  }                          
                                }
                                table inet firewalld {
                                  chain filter_FORWARD {
                           (8)      type filter hook forward priority 10; policy accept;
                            ↓       ct state established,related accept
                           (9)      ct status dnat accept
                                    iifname "lo" accept
                                    jump filter_FORWARD_IN_ZONES
                                    jump filter_FORWARD_OUT_ZONES
                                    ct state invalid drop
                                    reject with icmpx type admin-prohibited
                                  }
                                  chain filter_FORWARD_IN_ZONES {
                                    iifname "ens192" goto filter_FWDI_public
                                    goto filter_FWDI_public
                                  }
                                  chain filter_FORWARD_OUT_ZONES {
                                    oifname "ens192" goto filter_FWDO_public
                                    goto filter_FWDO_public
                                  }
                                  chain filter_FWDI_public { meta l4proto { icmp, ipv6-icmp } accept }
                                  chain filter_FWDO_public { jump filter_FWDO_public_allow }
                                  chain filter_FWDO_public_allow { ct state new,untracked accept }
                                }

現時点での通信は 10.254.10.105:random --> 172.20.0.4:1812 となります。
(1)forwardのhookの中で最も優先順位が高いので、filterを行うFORWARDというchainが選択される(pri:0)
(2)無条件にDOCKER-USERに飛ぶ
(3)なにもせず戻る
(4)無条件にDOCKER-ISOLATION-STAGE-1に飛ぶ
(5)なにもせず戻る
(6)出力IFはdocker1なので、DOCKERに飛ぶ
(7)入力IFはens192、出力IFはdocker1、DstAddrは172.20.0.4:1812なので、accept
  regular chain のDOCKERはbase chainのFORWARDから呼び出されている。
  DOCKER でacceptした時点で呼び出し元のFORWARDの評価がされ、このchainは終了する。
(8)forwardのhookの中で2番目に優先順位が高いので、filterを行うfilter_FORWARDというchainが選択される(pri:10)
(9)パケットはDNATされているので、accept
この時点の通信は最初と変わらず 10.254.10.105:random --> 172.20.0.4:1812 となります。

外部端末からのリクエスト(postrouting)

nft list ruleset からhookがpostroutingのものを抽出すると、以下となります。

                   table ip nat {
                     chain POSTROUTING {
(1)                    type nat hook postrouting priority 100; policy accept;
 ↓                     oifname "docker1" fib saddr type local COUNTER masquerade
 ↓                     oifname != "docker1" ip saddr 172.20.0.0/24 COUNTER masquerade
 ↓                     meta l4proto udp ip saddr 172.20.0.3 ip daddr 172.20.0.3 udp dport 514 COUNTER masquerade
 ↓                     meta l4proto udp ip saddr 172.20.0.5 ip daddr 172.20.0.5 udp dport 67 COUNTER masquerade
 ↓                     meta l4proto tcp ip saddr 172.20.0.2 ip daddr 172.20.0.2 tcp dport 8080 COUNTER masquerade
 ↓                     meta l4proto udp ip saddr 172.20.0.4 ip daddr 172.20.0.4 udp dport 1812 COUNTER masquerade
                     }
                     table ip firewalld {
                       chain nat_POSTROUTING {
(2)                    type nat hook postrouting priority 110; policy accept;
(3)->                    jump nat_POSTROUTING_ZONES
                       }
   ->(3)               chain nat_POSTROUTING_ZONES {
      ↓                  oifname "ens192" goto nat_POST_public
     (4)->               goto nat_POST_public
                       }
        ->(4)          chain nat_POST_public {
          (5)->          jump nat_POST_public_allow
                       }
             ->(5)     chain nat_POST_public_allow {
               (6)       oifname != "lo" masquerade
                       }
                     }
                   }

現時点での通信は 10.254.10.105:random --> 172.20.0.4:1812 となります。
(1) postroutingのhookの中で最も優先順位が高いのでnatを行うPOSTROUTINGというchainが選択される(pri:100)
  引き続きの処理がないため、policy適用 -> accept
(2) postroutingのhookの中で2番目に優先順位が高いのでnatを行うnat_POSTROUTINGというchainが選択される(pri:110)
(3) 無条件にnat_POSTROUTING_ZONESに飛ぶ
(4) 無条件にnat_POST_publicに飛ぶ
(5) 無条件にnat_POST_public_allowに飛ぶ
(6) 出力IFはdocker1なので、masquerade
  gotoで呼び出された先でchainが終了するため、policy適用 -> accept
  regular chain のnat_POST_public_allowはregular chain のnat_POST_publicから呼び出されている。
  regular chain のnat_POST_publicはregular chain のnat_POSTROUTING_ZONESからgoto命令で呼び出されている。
  goto命令で呼び出されたnat_POST_publicの処理が終了した時点で、呼び出したnat_POSTROUTING_ZONESが終了し
  それを呼び出したnat_POSTROUTINGも終了しpolicy accept が適用される。

masqueradeの処理を受け、最終的には 172.20.0.1:random --> 172.20.0.4:1812 となります。
(docker1から送出されるため、masqueradeで処理されると、送信元アドレスがdocker1になります)

radiusによる認証可否

radiusコンテナが受け取るリクエスト
172.20.0.1:random --> 172.20.0.4:1812

radiusサーバはその可否をチェックし、radiusクライアントに返答を返します。

radiusコンテナが返答するレスポンス
172.20.0.4:1812 --> 172.20.0.1:random

外部端末へのレスポンス

力尽きました。。。
nftablesでカウンタを仕掛けてみると、以下のchainを通過する際のアドレスが見えました。
1回の認証のやり取りなので、各chainで1パケットが見えていました。

type filter hook prerouting  : 172.20.0.4:1812 --> 172.20.0.1:random
type filter hook input       : 172.20.0.4:1812 --> 10.254.10.105:random
type filter hook forward     : 172.20.0.4:1812 --> 10.254.10.105:random
type filter hook postrouting : 172.20.0.4:1812 --> 10.254.10.105:random

radiusコンテナからの返答は、172.20.0.4:1812 --> 172.20.0.1:random であり、
着信時は自分宛の通信に見えるから、hook:inputを通過しているのは分かります。
その後、LocalProcessを通ってforwardに行く、のでしょうか?このあたりからよくわからなくなってしまいました。。。

中途半端になってしまった。。。

radiusからの応答パケットの経路がいまひとつわからない。
なぜどのchainの type:nat も通らないんだろう。。。
なぜ hook:inputhook:forward を同時に通っているんだろう。。。
table bridge filter の type:filter hook:input pri:-200 に入ってるのに
table ip filter の type:filter hook:input pri:0 には入って行ってないんだよなぁ。
L2のブリッジとL3のIPで違う処理をしているとか?

出典

https://knowledge.sakura.ad.jp/22636/
https://ja.wikipedia.org/wiki/Iptables
https://ja.wikipedia.org/wiki/Nftables
https://wiki.archlinux.jp/index.php/Nftables
https://wiki.archlinux.jp/index.php/Iptables
https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks
https://www.frozentux.net/iptables-tutorial/iptables-tutorial.html#TRAVERSINGOFTABLES
https://wiki.archlinux.jp/index.php/Nftables
https://knowledge.sakura.ad.jp/22636/
https://www.codeflow.site/ja/article/a-deep-dive-into-iptables-and-netfilter-architecture

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

Docker Composeでネットワークサービス群を5分で作れるようにした(dhcp/radius/proxy/tftp/syslog)

はじめに

CentOS7で各種ネットワークサービスを基本設定したメモ(dhcp/radius/proxy/tftp/syslog)
をCentOS8で再構築しました。今回は各サービスをDockerでコンテナに閉じ込めています。

自分で使う用に docker-compose を利用したところ、5分くらいで環境のリストアができるようになったので、記録に残します。

なにができるようになるのか

以下のような環境を作成できます。

Screenshot from Gyazo

docker-compose したホストの同一ポートにバインドしますので、外部端末から ホストのアドレス:サービスのポート にアクセスすると、各コンテナに到着します。
また、proxy,radius,dhcpは各種ログを同一ネットワーク上の syslog を待ち受けているコンテナにログを送信するようにしています。

以下のコンテナが生成されます。

server app address listen
proxy squid 172.20.0.2 8080/tcp
syslog rsyslog 172.20.0.3 514/udp
radius freeRADIUS 172.20.0.4 1812/udp
dhcp ISC-Kea 172.20.0.5 67/udp
tftp tftp-server - 69/udp
# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
b11308767849        infraserv:proxy     "/usr/sbin/init"    3 minutes ago       Up 3 minutes        0.0.0.0:8080->8080/tcp   proxy
33054f8b7d58        infraserv:tftp      "/usr/sbin/init"    35 hours ago        Up 2 hours                                   tftp
851ea861d04e        infraserv:syslog    "/usr/sbin/init"    35 hours ago        Up 2 hours          0.0.0.0:514->514/udp     syslog
dd3a657cfda2        infraserv:dhcp      "/usr/sbin/init"    35 hours ago        Up 2 hours          0.0.0.0:67->67/udp       dhcp
7249b9c4f11d        infraserv:radius    "/usr/sbin/init"    35 hours ago        Up 2 hours          0.0.0.0:1812->1812/udp   radius

以下のパラメータのネットワークが生成されます。

key value
name infraserv_infranet
subnet 172.20.0.0/24
interface docker1

tftpは --net=host な環境で動作しているため、 docker network は以下のような状態です。

# docker network inspect infraserv_infranet
[
    {
        "Name": "infraserv_infranet",
        "Id": "7ed8face2e4fec3110384fa3366512f8c78db6e10be6e7271b3d92452aefd254",
        "Created": "2020-02-15T05:37:59.248249755-05:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.20.0.0/24",
                    "Gateway": "172.20.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": true,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "7249b9c4f11de1f986892965671086d20957a6021269a5f5bc6dd85263bc0d70": {
                "Name": "radius",
                "EndpointID": "03ae6a9b9ff7817eea101955d2d6ff016982beb65c7dd6631c75c7299682c2dd",
                "MacAddress": "02:42:ac:14:00:04",
                "IPv4Address": "172.20.0.4/24",
                "IPv6Address": ""
            },
            "851ea861d04edeb5f5c2498cc60f58532c87a44592db1f6c51280a8ce27940bd": {
                "Name": "syslog",
                "EndpointID": "d18e466d27def913ac74b7555acc9ef79c88c62e62085b50172636546d2e72bb",
                "MacAddress": "02:42:ac:14:00:03",
                "IPv4Address": "172.20.0.3/24",
                "IPv6Address": ""
            },
            "b11308767849c7227fbde53234c1b1816859c8e871fcc98c4fcaacdf7818e89e": {
                "Name": "proxy",
                "EndpointID": "ffa6479b4f28c9c1d106970ffa43bd149461b4728b64290541643eb895a02892",
                "MacAddress": "02:42:ac:14:00:02",
                "IPv4Address": "172.20.0.2/24",
                "IPv6Address": ""
            },
            "dd3a657cfda211c08b7c5c2166f10d189986e4779f1dfea227b3afe284cbafec": {
                "Name": "dhcp",
                "EndpointID": "7371f4cf652d8b1bdbf2dc1e5e8ae97013a9a70b890c2caa36c2a7cc93b165df",
                "MacAddress": "02:42:ac:14:00:05",
                "IPv4Address": "172.20.0.5/24",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker1"
        },
        "Labels": {
            "com.docker.compose.network": "infranet",
            "com.docker.compose.project": "infraserv",
            "com.docker.compose.version": "1.25.3"
        }
    }
]

初期設定

後続の手順 で設定ファイルの修正をしない場合は、以下のパラメータで各サービスが動作します。

radius

初期状態では以下の2ユーザとenableパスワードが利用できるようになります。

ユーザ名 パスワード 備考
foo bar 通常ユーザでのログイン
hoge fuga Cisco機器にログインすると自動で特権に昇格
\$enab15\$ fuga Cisco機器のenableコマンドで遷移する際のパスワード

MACアドレスバイパスでは、以下のMACアドレスの場合に、ダイナミックVLAN用のアトリビュートを付与してAcceptされます。

MACアドレス VLAN文字列
112233445566 default_seg
aabbccddeeff default_seg

DHCP

リース情報

項目
リース時間 10時間
DNSサーバ 8.8.8.8

リース対象セグメント

セグメント レンジ GW
10.1.20.0/24 10.1.20.33 - 10.1.20.62 10.1.20.1
10.1.22.0/24 10.1.22.33 - 10.1.20.230 10.1.22.1

Option43によりアクセスポイントに送付するコントローラアドレス

対象 VCI文字列 コントローラアドレス
Cisco Cisco AP 10.254.10.201,10.254.10.202
Aruba ArubaAP 10.254.10.206

事前準備

CentOS と Docker と Docker Compose があれば、ここの手順はスキップで 作業内容 から始めてください。

CentOS8(10分)

CentOS8は最小構成で問題ありません。
もし手元になければ、 ESXi6.7にCentOS8を最小構成で構築 を参照してください。

Docker(5分)

dnf -y update
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf -y --nobest install docker-ce docker-ce-cli containerd.io
dnf -y update https://download.docker.com/linux/centos/7/x86_64/stable/Packages/containerd.io-1.2.10-3.2.el7.x86_64.rpm
dnf -y update
systemctl enable docker
systemctl start docker

Docker Compose(1分)

curl -L "https://github.com/docker/compose/releases/download/1.25.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

作業内容

firewall のポリシー追加(10秒)

firewall-cmd --add-service=tftp   --zone=public --permanent
firewall-cmd --add-masquerade   --zone=public --permanent
firewall-cmd --reload

各サービスのDockerコンテナをつくる(5分)

各サービスのDockerfileと設定ファイル、それらをまとめる docker-compose.yml は GitHub にあるものを使います。

git clone https://github.com/bashaway/infraserv

デフォルトの設定から変更する場合は、以下のエントリなどを参照してください。
設定ファイルの修正なしでもビルドには影響ありません。
ビルド後に修正しても大丈夫ですし。

DHCP
DockerでKea DHCPを構築。CiscoとArubaに同時にOption43が渡せるようになった。

RADIUS
DockerのFreeRADIUSでCiscoのログイン認証+MAC認証+ダイナミックVLANした(CentOS8)

Proxy
Dockerでproxyサーバ

TFTP
Dockerでtftpサーバ

Syslog
Dockerのrsyslogでコンテナ間や他サーバから転送されるログを集約した

設定ファイルの修正が終わったら、コンテナをつくります。

cd infraserv
docker-compose build
docker-compose up -d

いらなくなったら

コンテナ削除
docker-compose stop
docker-compose rm -f

イメージもいらなければ
docker-compose rmi -f

中身の解説

docker-compose.yml

docker-compose.yml は、以下のように構成されています。

docker-compose.yml
version: '3'

services:

  proxy:
    build: ./proxy
    image: infraserv:proxy
    container_name: proxy
    hostname: proxy
    restart: always
    networks:
      infranet:
        ipv4_address: 172.20.0.2
    ports:
      - 8080:8080
    cap_add:
      - SYS_ADMIN
    security_opt:
      - seccomp:unconfined
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    environment:
      TZ: 'Asia/Tokyo'

  syslog:
    build: ./syslog
    image: infraserv:syslog
    container_name: syslog
    hostname: syslog
    restart: always
    networks:
      infranet:
        ipv4_address: 172.20.0.3
    ports:
      - 514:514/udp
    cap_add:
      - SYS_ADMIN
    security_opt:
      - seccomp:unconfined
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    environment:
      TZ: 'Asia/Tokyo'

  radius:
    build: ./radius
    image: infraserv:radius
    container_name: radius
    hostname: radius
    restart: always
    networks:
      infranet:
        ipv4_address: 172.20.0.4
    ports:
      - 1812:1812/udp
    cap_add:
      - SYS_ADMIN
    security_opt:
      - seccomp:unconfined
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    environment:
      TZ: 'Asia/Tokyo'

  dhcp:
    build: ./dhcp
    image: infraserv:dhcp
    container_name: dhcp
    hostname: dhcp
    restart: always
    networks:
      infranet:
        ipv4_address: 172.20.0.5
    ports:
      - 67:67/udp
    cap_add:
      - SYS_ADMIN
    security_opt:
      - seccomp:unconfined
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    environment:
      TZ: 'Asia/Tokyo'

  tftp:
    build: ./tftp
    image: infraserv:tftp
    container_name: tftp
    hostname: tftp
    restart: always
    network_mode: host
    cap_add:
      - SYS_ADMIN
    security_opt:
      - seccomp:unconfined
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    environment:
      TZ: 'Asia/Tokyo'


networks:
  infranet:
    driver: bridge
    driver_opts:
      com.docker.network.bridge.enable_ip_masquerade: "true"
      com.docker.network.bridge.host_binding_ipv4: "0.0.0.0"
      com.docker.network.bridge.name: "docker1"
    ipam:
      config:
        - subnet: 172.20.0.0/24

さいごに

Dockerでコンテナにしておくと、ホスト機が汚れなくて気持ちいい

出典

http://docs.docker.jp/engine/reference/commandline/toc.html

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

Docker上でGUIのROSを一瞬でセットアップする方法

docker-ros-desktop-vnc.png

Docker上でGUIのROSを動かす

 TiryohさんがGitHub上で素晴らしいリポジトリを公開していました。

Tiryoh/docker_ros-desktop-vnc(GitHub)

 素晴らしさに感動してしまったので、素晴らしさを少しでも広めるために、簡単な紹介記事を書いてみます。

 このリポジトリは、Docker上でGUIのROSを動かすことができます。Dockerさえ入っていればMacでもLinuxでも(多分)Windowsでも、一瞬でUbuntu+ROSの環境が手に入ります。ROSはセットアップがそれなりに大変なので、Dockerさえ入っていればコマンド一発で環境構築できるのは最高ですね。

 Dockerって何?という方やインストール方法が分からない方は、以前私のブログに書いた下記記事を参照下さい。

Docker入門して機械学習環境構築

 Dockerをインストールしたら、後は以下のコマンドを実行するだけです。

$ docker run -p 6080:80 -v /dev/shm:/dev/shm tiryoh/ros-desktop-vnc:melodic

 初回は、イメージのダウンロードから始まるので時間かかります(2回目以降はすぐ起動します)。

 Docker上でVNCが走っているので、リモートログインができます。特殊なソフトは必要なくて、好きなブラウザで以下のアドレス(自分のPCのアドレス)にアクセスするだけです。

http://127.0.0.1:6080/

 これで、ブラウザ上でROS環境が動きます。ヒュー!最高ですね。あとは、2つターミナル起動して、それぞれに以下のようにコマンドを打つと、おなじみのカメさんのシミュレータが動きます。

$ roscore
$ rosrun turtlesim turtlesim_node

docker-ros-desktop-vnc.png
 カメさん!

 うれしくて、Twitterで呟いたら、TiryohさんからROS2版もあるよというコメント。まさに欲しかったやつです。最高かよ!ブログ記事も楽しみです!

 ROS2版のGitHubリポジトリは以下です。ROS版と同じ要領で動かせます。

Tiryoh/docker_ros2-desktop-vnc(GitHub)

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

Docker上でGUIのROS1/ROS2を一瞬でセットアップする方法

docker-ros-desktop-vnc.png

Docker上でGUIのROSを動かす

 TiryohさんがGitHub上で素晴らしいリポジトリを公開していました。

Tiryoh/docker_ros-desktop-vnc(GitHub)

 素晴らしさに感動してしまったので、素晴らしさを少しでも広めるために、簡単な紹介記事を書いてみます。

 このリポジトリは、Docker上でGUIのROSを動かすことができます。Dockerさえ入っていればMacでもLinuxでも(多分)Windowsでも、一瞬でUbuntu+ROSの環境が手に入ります。ROSはセットアップがそれなりに大変なので、Dockerさえ入っていればコマンド一発で環境構築できるのは最高ですね。

 Dockerって何?という方やインストール方法が分からない方は、以前私のブログに書いた下記記事を参照下さい。

Docker入門して機械学習環境構築

 Dockerをインストールしたら、後は以下のコマンドを実行するだけです。

$ docker run -p 6080:80 -v /dev/shm:/dev/shm tiryoh/ros-desktop-vnc:melodic

 初回は、イメージのダウンロードから始まるので時間かかります(2回目以降はすぐ起動します)。

 Docker上でVNCが走っているので、リモートログインができます。特殊なソフトは必要なくて、好きなブラウザで以下のアドレス(自分のPCのアドレス)にアクセスするだけです。

http://127.0.0.1:6080/

 これで、ブラウザ上でROS環境が動きます。ヒュー!最高ですね。あとは、2つターミナル起動して、それぞれに以下のようにコマンドを打つと、おなじみのカメさんのシミュレータが動きます。

$ roscore
$ rosrun turtlesim turtlesim_node

docker-ros-desktop-vnc.png
 カメさん!

 うれしくて、Twitterで呟いたら、TiryohさんからROS2版もあるよというコメント。まさに欲しかったやつです。最高かよ!ブログ記事も楽しみです!

 ROS2版のGitHubリポジトリは以下です。ROS版と同じ要領で動かせます。

Tiryoh/docker_ros2-desktop-vnc(GitHub)

まとめ

 Docker環境のGUIにブラウザでアクセスできるの、めちゃ良いですね。この組み合わせは他にも色々応用効きそうです。

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