Help us understand the problem. What is going on with this article?

Dockerで開発環境を仮想化する

More than 1 year has passed since last update.

ことの発端

時々、提供されるソフトのバージョンが変わってコンパイラのバージョンが変わることがあったりします。
しかし、自分たちの膨大な数のmakefileにはgccとかclangとかしか書かれてないのです。
これでは去年の製品を再ビルドしようにも、シンボリックリンク先が新環境を指しているのでダメです。

しかし、製品サポート案件で容赦なく過去モデルでのバグ修正チケットが回ってきます。
先輩に聞くと「コンパイラやライブラリのディレクトリ名をリネームする」と。
なん、だと……という感じです。今の環境が正しいのかすら保証できません。

これでは、バグ以前に昔のモデルを再現することすら難しいです。
もう、xx年用ビルド環境を個別に用意するしかありません。
しかし、マシンを複数用意してくれる訳もなく(してもらってもスペース的に困りますが)、そこで仮想化の手段に手を出し始めました。
幸いにも、マシンに基本的人権は備わっていたのです。

注意点1: コンテナ型VMの特徴は分かっておいてね

Dockerはコンテナ型VMと言われるモノで、プロセスとして動きます。
つまり、ホストとゲストがKernelを共有することになります。
これが許容できる場合にのみ行ってくださいね。

ホストと開発環境が違うKernelで動いている場合は、KernelからVMするホスト型VM(※1)を使う必要があります。
その場合は、Vagrantで自動化できるのでそちらを勉強してみてください。

※1...VirtualBoxやVM Wareなど

注意点2: DockerfileはLintしようね

Dockerfileの更新を最新の記述方式に合わせるようにはわざわざ追従していません

hadolintというLintソフト(書式を検証してくれる)があるので、それを正義にしてください。
英語ですけど、すっごい簡単にコマンド一発で動かせます。

注意点3: この記事、あんまりよろしくないらしい

今のチームに、人間辞めてそうな人が帰ってきたんです。
その人も勝手にDocker使って環境立ち上げてて、「おっやるやん?」とおこがましくも思っていました。
この「環境を仮想化しよう運動」が実りを見せ始めて、Dockerfileをもらったんですけど、意味不明でした。

Dockerのポリシーである「Dockerは一瞬だけ立ち上げて、仕事が終わったら潰す」を本当に地で実践していたんです。
makeコマンドを叩いた一瞬だけ立ち上がってビルドし、ビルドが終わればコンテナを潰しているんです。
Makefileにdockerコマンドを書くなんて発想微塵もしなかったですね(´・_・`)

完全に僕の負けです。(別に勝負してるわけじゃないんですけど
この運動が最後まで完走出来たら、核なんだけど外部に公開できる部分だけを抽出して、それが意味を持てるレベルならQiitaに新しく書こうと思います。。。
最近、TILに書いてるし、そっちかもしれませんけど。。。

環境

OS:Windows10 -(Virtual Box)-> ubuntu 16.04 -(Docker)-> ubuntu 14.04
Docker:17.03

Dockerの導入

apt-getで手に入るDockerはなんだか古い感じがしたので、本家のドキュメント通りに導入しました
今は、Docker Engineって名前じゃなくて、2種類ぐらいに分かれているようです
Docker blogに記事が上がっています

Docker 説明
Docker Engine 旧バージョンのDocker。使わないでってあります。
Docker CE
Community Edition
個人用の無料版です。
Visual Studio Communityみたいなもんでしょうか
Docker EE
Enterprise Edition
法人用のサポート付きです。あんまり良く分かりません。

Dockerする前に

dockerを色々やっていく前に、Proxy環境だとのっけからコケるので一つだけ。
Proxy環境でない方は、飛ばしてください。

Proxy設定

これから使うFROMコマンドでは、docker hubあたりから該当するイメージをDLします。
しかし、Proxyサーバーをdockerが認識していないと外部ネットワークにつながらなくて実行できません。
こっちにまとめました。

upstartの人やsystemdの人を分けてあります。
※systemdちょっと自信ないです。今度systemd環境でやる機会があるので修正予定はあります。(多分)
Proxy環境でdockerを外に繋ぐ方法

Dockerやるぞい

それでは、これからDockerfileを作っていきます。
好きな作業ディレクトリで行いましょう。
私は$HOME/Dockerでやってます。

Dockerfileの作成

ベースとなるOSの選択とディレクトリの移動をしておきましょう。
ディレクトリが存在しなくても勝手に作ってくれます。

Dockerfile
# 仮想化するベースOS
FROM ubuntu:14.04
# 誰が作ったかは入れておきましょう
MAINTAINER tester
# ディレクトリの移動
WORKDIR /home/tester

イメージをビルドする

Dockerfileを作ったら、それをイメージにします。

HOST
docker build -t qiita:0.1 ./
  • -t/--tag REPOSITORY NAME:TAG NAME
    イメージのリポジトリ名:タグ名を設定できます。
  • ./
    Dockerfileのあるパスを指定します。
    今は、Dockerfileのあるパスで作業している前提なのでカレントディレクトリを指定しています。

ボリュームの設定

今回のDockerはビルドサーバーとして用意しています。
Dockerを止めた時、ビルドした結果も消えてしまいます。非常に残念ですね。
そこでホスト側のディレクトリをコンテナ側でマウントして、ホスト側に保存してやりましょう。

Dockerの世界では共有ディレクトリを「ボリューム」と言うそうです。
ボリュームは、コンテナ側ではなくてホスト側から指定するのでコマンド上で行います。

HOST
$ docker run --name test -it --rm -v $HOME/Docker:/home/tester/share qiita:0.1
  • -it
    teratailのこの質問が一番わかり易かったです。
  • --rm
    コンテナの終了時に、dockerプロセス欄からも消すオプション
  • --volume / -v ホスト側のパス:コンテナ側のパス
    共有ディレクトリを貼るオプション

これで、ホストとコンテナは繋がりました。
このディレクトリに保存すると、コンテナを停止してもファイルが残ります。
安心してgit cloneしていいです。

アクセス権について

dockerを起動したので、共有ディレクトリにファイルを保存してみます。

CONTAINER
# touch /home/tester/share/from_root_docker.txt
# exit

ホスト側からアクセスしてみましょう。

HOST
$ ll $HOME/Docker
-rw-r--r-- 1 root root 0 yy-mm-dd HH:MM:SS from_root_docker.txt

確かにコンテナをexitしてもファイルが残り続けています!!
コンテナの寿命という円環の理から外れることができました!

……が、どう見てもrootユーザー、rootグループ。
一般ユーザーにはr権限しかありません。ありがとうございました。
これでは、コンテナ側でgit cloneしたファイルをホスト側で変更するのに毎回sudo権限を使う必要があります。
メンドウですね……

一般ユーザーの追加

root権限でファイルが生成されるのは非常にメンドウなので、一般ユーザーでやってみましょう。
普通にユーザーを追加するコマンドを自動実行してもらいます。

Dockerfile
# 仮想化するベースOS
FROM ubuntu:14.04
# 誰が作ったかは入れておきましょう
MAINTAINER tester

# 各環境変数を設定
ENV USER tester
ENV HOME /home/${USER}
ENV SHELL /bin/bash

# 一般ユーザーアカウントを追加
RUN useradd -m ${USER}
# 一般ユーザーにsudo権限を付与
RUN gpasswd -a ${USER} sudo
# 一般ユーザーのパスワード設定
RUN echo "${USER}:test_pass" | chpasswd

ハマったポイント

  • useraddコマンドには-mが必須
    useraddコマンドだけだと、ホームディレクトリがアクセス権root:rootで作られてしまいます。
    理由はよくわかりませんが、作業すべてにsudoを要求されてとてつもなく不便です。
    adduserコマンドを使うか、useradd -mを使うとアクセス権tester:testerでホームディレクトリが作られました。
    adduserとuseraddの違いはこちら
    簡単に言うと、adduserは対話的、useraddは独話的とのことなので、useraddを使っています。

  • chpasswdは引数を取らない
    chpasswdなんてコマンドは初めて知ったのですが、root限定コマンドらしいです(当然)
    複数ユーザーのパスワード一括設定ができます。
    が、これ、引数を取りません。データファイルをパイプで流すみたいです。
    今回は一人分なのでechoからパイプしました。

HOST
$ docker build --tag qiita:0.2 ./

いざ、一般ユーザーでコンテナを起動!

一般ユーザーで起動する方法は、オプション指定する方法とDockerfileに書いてしまう方法の2種類があります。
色々な運用があるので一概には言えないと思いますけど、毎回userを選択させられるのはメンドウなので2番目の手法がオススメです。

方法1:オプションで起動する方法

ハマったポイントなんですけど、Dockerでユーザー指定するとき、
私はホスト側のアカウント設定をロードして、その設定を引き継いでくれるのかな、なんて思っていたんです。
なんか、resolv.confはそんな感じらしいじゃないですか。
ホスト側設定(/etc/passwd)を引き継がないので注意です。

Dockerのコマンドでユーザーオプション(-u)を使う場合は、出来上がる仮想環境下のアカウント設定にユーザーデータを作る必要があります。
まぁ、オプションを使わなくても設定しといて問題ない(と思う)ので設定しときましょう。

Dockerfile
# 仮想化するベースOS
FROM ubuntu:14.04
# 誰が作ったかは入れておきましょう
MAINTAINER tester

# 各環境変数を設定
ENV USER tester
ENV HOME /home/${USER}
ENV SHELL /bin/bash

# 一般ユーザーアカウントを追加
RUN useradd -m ${USER}
# 一般ユーザーにsudo権限を付与
RUN gpasswd -a ${USER} sudo
# 一般ユーザーのパスワード設定
RUN echo "${USER}:test_pass" | chpasswd
# ログインシェルを指定(アカウント情報の作成)
RUN sed -i.bak "s#${HOME}:#${HOME}:${SHELL}#" /etc/passwd
HOST
$ docker run --name test --u tester -it --rm -v $HOME/Docker:/home/tester/share qiita:0.2
  • --user / -u ユーザー名
    コンテナ内の/etc/passwdに登録されているユーザーでログインする

方法2:Dockerfileに書いておく方法

Dockerfile
# 仮想化するベースOS
FROM ubuntu:14.04
# 誰が作ったかは入れておきましょう
MAINTAINER tester

# 各環境変数を設定
ENV USER tester
ENV HOME /home/${USER}
ENV SHELL /bin/bash

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

# 以降のRUN/CMDを実行するユーザー
USER ${USER}
# 以降の作業ディレクトリを指定
WORKDIR ${HOME}

※なんかkobitoでシンタックスハイライトしてくれませんけど、ちゃんと動きます。

USERコマンドを使うと、以降のRUNコマンド、CMDコマンドをそのユーザーで行うようになるようです。
docker runコマンドでuserオプションを指定しなくても良くなります。
このコマンドは対象のUSERでログインすると思えばいいんじゃないでしょうか。

HOST
$ docker build --tag qiita:0.2 ./
$ docker run --name test -it --rm -v $HOME/Docker:/home/tester/share qiita:0.2

ファイルを作成してみる

git cloneとかしてみてもいいですけど、適当にtouchコマンドで空ファイルを作ってみます。

CONTAINER
$ touch /home/tester/share/from_tester_docker.txt
$ exit

ホスト側でファイルを確認する

HOST
$ ls -l
-rw-r--r-- 1 root         root         0 YY-MM-DD hh:mm:ss from_root_docker.txt
-rw-r--r-- 1 host_user    host_user    0 YY-MM-DD hh:mm:ss from_tester_docker.txt

あ、あれ!? コンテナ側のtesterユーザーでファイル作ったのに、ホスト側のユーザー名になってる!?
多分、ファイルはuid/gidなどの番号で管理してるんじゃないでしょうか。

場所 ユーザー名 UID GID
コンテナ tester 1000 1000
ホスト ログインユーザー名 1000 1000

※uid/gidはデフォルト1000番で、ユーザーが増えるごとに1001, 1002...と増えていくようです。

番号でしか管理していないから、ホストで見た時はホスト側のユーザーが所有者になるんじゃないかな、と。
根拠全く無いですけど。

終わり

gccとかclangとかgolangとかpythonとかのソフトウェアは別途インストールしてくださいね。
その時は、ぜひともDockerfileに書いてやってください。

それでは、よいdocker生活をノシ

Riliumph
しがないC++/Pythonが大好きなエンジニア。 Java(Servlet/JSP), HTML5, C/C++(with Boost), Python3, C#とか色々仕事してきました。 今は組み込みC言語やってます。 もっと羽振りのいい会社に行きたいよね(´・_・`)ハァー
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした