[Debian] chef/Serverspec のためだけに CoreOSのDockerを構成する [Wheezy]


流行に従って、Dockerを使い始めている最近です。どうせなら、CoreOS使ってやろうとCoreOS+Dockerで、chef/Serverspecの開発環境を作るに至ったのわけだけれども、systemd を初めて触ったり、/etc 配下をみてもサッパリ意味が分からなかったり、構成変更どうやったらいいのか、まるで宇宙に取り残された気分になりました。
そんな宇宙に取り残された状態でもNet上に散らばった情報をまとめていって、なんとか自分で満足のいくCoreOS+Docker環境が完成しました。

ので、自分のためだけに書き残しておきます。

# CoreOSをインストールしよう

CDブートからCoreOSをインストールする方法を選択します。この1週間で10回以上はインストールしていますが、全てこの方法でクリーンインストールを繰り返しています。しかし、cloud-config.ymlという素敵な構成ファイルのお陰で、何度やりなおしても苦痛がないので助かっています。ホント素晴らしい。
さて、まずはメディアを準備しましょう。

## ブートCDを準備する

[Booting CoreOS from an ISO](https://coreos.com/docs/running-coreos/platforms/iso/) より好きなメディアを落としてきましょう、どうせイメージはネットからダウンロードしてインストールするので、どれでもいい気がします(個人的見解)。

仮想環境でも実機でもどこでもいいので、ISOのまま使うか、CDに焼くかは皆さんのお好み次第です。私は Proxmoxの仮想環境に入れてます。
起動するだけなら、ディスクサイズは2GBと必要ないでしょう。とは言え、イメージをおいたりするので、20-40GB と自分がどれだけのイメージを準備するかによって割り当てましょう。
我が家では、外部NFSをマウントしたりして、共有やデータの外部保管も実現してるので、16GBと少なめです。イメージ一つだけですしね。

## cloud-config.yml を準備する

インストールする前に、まず設定ファイルを準備しておきます。面倒ごとは先にする主義です。実現してないけど。

内容はコレだ(ﾊﾞﾊﾞﾝ)長い

```:cloud-config.yml
#cloud-config

hostname: docker01.oshiire.to

coreos:
  units:
    - name: etcd.service
      command: stop
    - name: fleet.service
      command: stop
    - name: docker-tcp.socket
      command: start
      enable: yes
      content: |
        [Unit]
        Description=Docker Socket for the Remote API

        [Socket]
        ListenStream=0.0.0.0:2375
        Service=docker.service
        BindIPv6Only=both

        [Install]
        WantedBy=sockets.target
    - name: enable-docker-tcp.service
      command: start
      content: |
        [Unit]
        Description=Enable Docker Socket for Remote API

        [Service]
        type=oneshot
        ExecStart=/usr/bin/systemctl enable docker-tcp.socket
    - name: timezone.service
      command: start
      content: |
        [Unit]
        Description=timezone
        [Service]
        Type=oneshot
        RemainAfterExit=yes
        ExecStart=/usr/bin/ln -sf ../usr/share/zoneinfo/Japan /etc/localtime
    - name: 10-static.network
      runtime: no
      content: |
        [Match]
        Name=ens*

        [Network]
        Address=...
        Gateway=...
        DNS=...
    - name: rpc-statd.service
      command: start
    - name: home.mount
      command: start
      content: |
        [Unit]
        Description=Home Directory on NFS
        Before=rpc-statd.service
        Conflicts=umount.target

        [Mount]
        What=...:/mnt/home
        Where=/home
        Options=rw,rsize=4096,wsize=4096,hard,intr,async,nodev,nosuid
        Type=nfs
  update:
    reboot-strategy: best-effort

users:
  - name: core
    passwd: ...
    groups:
      - sudo
      - docker
    ssh-authorized-keys:
      - ssh-rsa AAAA.....==
```

拡張子から分かるとおり、YAMLで記述されています。
個人的に見られたくない部分は「...」としています。主に書き換え必須な部分はそこだろうと当たりをつけてください。また、indent はスペースでないとダメらしいです。こわいですね。

さて、自分のために、全部説明書きを、残すんです(川平風)

### #cloud-config

```:cloud-config.yml
#cloud-config
```

おまじないです。絶対に入れておいてください。ないと死にます。

### hostname

```:cloud-config.yml
hostname: docker01.oshiire.to
```

家庭内で使っているホスト名なので、外から叩いても何も起きません。とりあえず、好きにつけてください。名前解決できるようにしておけるものが望ましいです。

### coreos:  units:

systemd で処理されるメインの部分です。
name: ごとに各daemon や service が割り当てられていると判断しました。(systemdがよく分かってない)

```:cloud-config.yml
coreos:
  units:
```

おまじないです。忘れずに書きましょう。

```:cloud-config.yml
    - name: etcd.service
      command: stop
    - name: fleet.service
      command: stop
```

etcd と fleet を使わないと心に誓った想いがここに現れています。
CoreOSとしては etcd/fleet がメインみたいなので、勝手に起動するように設定されそうなので先に排除しました。今回は `Docker` に絞ったのでこんな感じにしてます。

```:cloud-config.yml
    - name: docker-tcp.socket
      command: start
      enable: yes
      content: |
        [Unit]
        Description=Docker Socket for the Remote API

        [Socket]
        ListenStream=0.0.0.0:2375
        Service=docker.service
        BindIPv6Only=both

        [Install]
        WantedBy=sockets.target
    - name: enable-docker-tcp.service
      command: start
      content: |
        [Unit]
        Description=Enable Docker Socket for Remote API

        [Service]
        type=oneshot
        ExecStart=/usr/bin/systemctl enable docker-tcp.socket
```

メイン部分の `Docker` 起動部分ですね。Docker プロセス自体は勝手に起動されてくるようなんですが、remote APIが使えないので、寂しすぎるので `tcp socket` サービスを入れて、外部からも `docker コマンド` を使えるようにしています。

`ListenStream=0.0.0.0:2375` 部分がキーポイントです。ListenするIPアドレスとポートを指定できます。このケースだと、IPv4 で割り当てられた全I/F から `2375/tcp` ポートで待ち受けることを示してます。

### timezone.service

どこからかパクってきました。最低です。
ここは日本であることを示して、Timezoneを設定してます。時刻は正しく表示してもらいたいですもんね!

```:cloud-config.yml
    - name: timezone.service
      command: start
      content: |
        [Unit]
        Description=timezone
        [Service]
        Type=oneshot
        RemainAfterExit=yes
        ExecStart=/usr/bin/ln -sf ../usr/share/zoneinfo/Japan /etc/localtime
```

### 10-static.network

数字始まりでなにやら異質な部分ですが、ネットワーク設定部分です。

```:cloud-config.yml
    - name: 10-static.network
      runtime: no
      content: |
        [Match]
        Name=ens*

        [Network]
        Address=...
        Gateway=...
        DNS=...
```

`[Network]` 以下の `Address=xxx.xxx.xxx.xxx/xx` にIPアドレスを、`Gateway=xxx.xxx.xxx.xxx` に Default gatewayアドレスを、`DNS=xxx.xxx.xxx.xxx` に DNSサーバのIPアドレスを指定します。DNSが複数あるときは、もう1行 `DNS=...` を追加すれば良さそうです。

### home.mount

我が家では慣習的に /home を nfsマウントして運用している手前、nfs マウントを追加しています。お好みですが、なかなか情報が無かったので、systemd の書き方探してきて、自分なりにアレンジしました。オレカッコイイ。

```:cloud-config.yml
    - name: rpc-statd.service
      command: start
    - name: home.mount
      command: start
      content: |
        [Unit]
        Description=Home Directory on NFS
        Before=rpc-statd.service
        Conflicts=umount.target

        [Mount]
        What=...:/mnt/home
        Where=/home
        Options=rw,rsize=4096,wsize=4096,hard,intr,async,nodev,nosuid
        Type=nfs
```

nfs の lock サービスを使いたいので、`rpc-statd` を起動するようにしています。最初の2行がそれですね。

systemd の慣習として、`xxx.mount` の `xxx` はマウント先を書くようなので、今回は `home.mount` としています。要するに好きにしろってコトです。

`[Unit]` 以下には、先に `rpc-statd.service` が起動していて欲しい、と言うことと、`umount.target` の仲間にして下さいと言うことで `Conflicts` を指定してます。詳しいことはよく分かりません。

`[Mount]` には、`/etc/fstab` に書いているようなことを記載します。

`What` は、マウント元です。`...`はマウント元のサーバ名またはIPアドレスで、`:` をはさんでディレクトリを示します。この例だと `/mnt/home` がマウント元のディレクトリですね。
`Where` は、この`CoreOS`でマウントする先になります。先にも説明したとおり、`home` を共有しているので、`/home`としています。この場合、`CoreOS` のホームディレクトリが `/home` 配下になりますので、その辺はうまいことやりましょう。
`Options` は、nfsのマウントオプションです。うちはこんな感じです。
`Type` には、マウントタイプを指定します。nfs じゃなくて smbfs や ext4 なども指定できますので、適宜読みかえましょう。

### update

CoreOS はアップデートを見つけると勝手に更新して、勝手に再起動します。素敵ですね。
その時の再起動方針を指定します。

```:cloud-config.yml
  update:
    reboot-strategy: best-effort
```

[Using Cloud-Config](https://coreos.com/docs/cluster-management/setup/cloudinit-cloud-config/) の `update` セクションに詳しいので、そちらを見てください。
なお、ぱくってくるとこんなコト書いてあります。

> One of "reboot", "etcd-lock", "best-effort" or "off" for controlling when reboots are issued after an update is performed.
> 
> * reboot: Reboot immediately after an update is applied.
> * etcd-lock: Reboot after first taking a distributed lock in etcd, this > guarantees that only one host will reboot concurrently and that the cluster will remain available during the update.
> * best-effort - If etcd is running, "etcd-lock", otherwise simply "reboot".
> * off - Disable rebooting after updates are applied (not recommended).

いつでもすぐに再起動して欲しければ`reboot`、再起動して欲しくなければ`off`、なんだからよく分からなければ`best-effort`、etcd 使ってる人は `etcd-lock`と`best-effort`あたりで悩んでください。

### users

長かった(個人的に)...。最後に usersです。CoreOSで利用するユーザをここに指定します。書かないとログインで困ります。

```:cloud-config.yml
users:
  - name: core
    passwd: ...
    groups:
      - sudo
      - docker
    ssh-authorized-keys:
      - ssh-rsa AAAA.....==
```

`name` 欄にユーザ名を記載します
`passwd` には、[Using Cloud-Config](https://coreos.com/docs/cluster-management/setup/cloudinit-cloud-config/) の `Generating a password hash` セクションにある方法で password のハッシュを作って、それを記載します。

```bash:commands
# On Debian/Ubuntu (via the package "whois")
mkpasswd --method=SHA-512 --rounds=4096

# OpenSSL (note: this will only make md5crypt.  While better than antext it should not be considered fully secure)
openssl passwd -1

# Python (change password and salt values)
python -c "import crypt, getpass, pwd; print crypt.crypt('password', '\$6\$SALT\$')"

# Perl (change password and salt values)
perl -e 'print crypt("password","\$6\$SALT\$") . "\n"'
```

`groups` には所属グループを記載しますが、`sudo` に入れとかないとむせび泣くと思います
`ssh-authorized-keys` には、ssh の公開鍵をそのままぺたっと貼り付けます。我が家ではRSA鍵使ってるので、ssh-rsa から始まるあれが貼り付けてあります。これがそのまま `~/.ssh/authorized_keys` になります。

長かった...。

## インストールしよう!

の前に、コンソールで、上記 `cloud-config.yml` を入力するのは至難の業なので、次の順に進めていきます。

1. `core` ユーザにパスワードをつける
2. IPアドレスを知る
3. 自由の利くクライアントから ssh ログインする
4. `cloud-config.yml` をコピペするか、scp でコピーする
5. インストールコマンドを打つ

### 1. `core` ユーザにパスワードをつける

起動した CoreOSでおもむろに次のコマンドを打ちましょう。

```bash:CoreOS
sudo passwd core
```

パスワードは好きにつけてください

### 2. IPアドレスを知る

```bash:CoreOS
ip -f inet addr
```

実は Enterキーを押しても IPアドレス分かります。何故こんなコトするかというと、dhcp で勝手に拾ってくるからです。dhcp ない人は諦めてください(やり方知らない)。

### 3. 自由の利くクライアントから ssh ログインする

先の二つの経験を元に

```bash:ssh
ssh core@[ipアドレス]
```

でどうぞ。`~/.ssh/known_hosts` が汚されたくない人は、`-o "StrictHostKeyChecking no"` つけてください。コレによる影響は多少は気にしたほうがいいです。

### 4. `cloud-config.yml` をコピペするか、scp でコピーする

多分 `vi` できますので、`vi ~/cloud-config.yml` したり、`scp ./cloud-config.yml core@xxx.xxx.xxx.xxx:~/` で、先ほどの `cloud-config.yml` をなんとかしましょう。

### 5. インストールコマンドを打つ

最後です。これを打てばインストールできます。上記の設定で。素敵。

```bash:CoreOS
sudo coreos-install -d /dev/sda -C stable -c ~/cloud-config.yml -V 493.0.0
```

各オプションは次の通り

`-d` 導入先のディスクを指定します。
`-C` 導入する `CoreOS` のチャネルを指定します。`stable`(安定版) `beta`(β版) `alpha`(開発最新版) とあり、安定志向の人は`stable`を、最新版がとにかく使いたい人は`alpha`を、引っ込み思案で優柔不安なあなたは`beta`を指定しましょう。
`-c` 大文字と小文字に注意。これまでがんばって作り上げた`cloud-config.yml`ファイルを指定します。
`-V` 導入するバージョンを指定します。個人的には指定した方が良いと思いますが、各チャネルの最新バージョンは[Release Channels](https://coreos.com/releases/)で確認できますので、ここを見ながら指定しましょう。

## 再起動

導入完了の合図がきたら、あとは再起動するのみです。
おめでとうございます、CoreOSの導入はここで完了です!

長かったなー。とお思いでしょうが、コレでは終わりません。大変です。

# Debian用の chef/serverspec 用 Dockerイメージを準備する

なんだよ、フツーに docker build すればいいんだろと思われるかも知れませんが、実はちょっと注意事項があるんです。

私の `Dockerfile` をご覧に入れましょう。大盤振る舞いです。

```:Dockerfile
FROM debian:wheezy

MAINTAINER sho kisaragi <sho@oshiire.to>

RUN echo "deb http://ftp.jp.debian.org/debian/ wheezy main" > /etc/apt/sources.list
RUN echo "deb http://security.debian.org/ wheezy/updates main" >> /etc/apt/sources.list
RUN echo "deb http://ftp.jp.debian.org/debian/ wheezy-updates main" >> /etc/apt/sources.list

RUN apt-get update
RUN apt-get install -y ca-certificates dialog locales openssh-server sudo curl rsync net-tools --no-install-recommends
RUN apt-get upgrade -y && apt-get clean

RUN mkdir /var/run/sshd

RUN groupadd --gid 1000 chef
RUN useradd  --uid 1000 --gid 1000 -m chef
RUN mkdir    -p   /home/chef/.ssh
RUN chmod    0700 /home/chef/.ssh
RUN chown chef.chef /home/chef/.ssh
RUN echo 'chef ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers.d/chef

ADD id_rsa.pub    /home/chef/.ssh/authorized_keys
RUN chmod    0400 /home/chef/.ssh/authorized_keys
RUN chown chef.chef /home/chef/.ssh/authorized_keys

RUN echo "#!/bin/sh\nexit 0" > /usr/sbin/policy-rc.d

RUN curl -L https://www.opscode.com/chef/install.sh | sudo bash

EXPOSE 22

CMD ["/usr/sbin/sshd", "-D"]
```

何の変哲もない気がします。流れとしては、chefを実行するために必要なパッケージを入れることと、専用のユーザを準備しているだけに見えます。

残念、そうはいかないのです。

## invoke-rc.d: policy-rc.d denied execution of start の罠

なんとなく docker上で chefを運用していると、`invoke-rc.d: policy-rc.d denied execution of start.` に巡り会い、service が起動できない自体に巡り会うことがあります。その理由は [Docker, Openstack, policy-rc.d, mysqld](http://monkinetic.com/2014/07/25/docker-openstack-policy-rcd-mysqld) にもある通り、systemd 化された CoreOSのために、`policy-rc.d` コマンドが `exit 101` を強制的に返すことによる影響を受けるためです。分かっていればいいわけですが、地味にいやな気持ちになるので、`Dockerfile` 内に次の 1行を入れています。

```:Dockerfile
RUN echo "#!/bin/sh\nexit 0" > /usr/sbin/policy-rc.d
```

これでいいのか的な対応ですが、いいんです(川平風)

ここまでやって、やっと 「debian向けchef/serverspec開発用環境 on CoreOS」の完成です。
文章は無駄に長いですが、やってみると 5分で終わります。是非どうぞ。