【自宅サーバー】Mac miniにCoreOSをインストールしKubernetesを運用する

Mac mini に CoreOS をインストールして、kubernetes を立ち上げて自宅サーバーを運用するまでの話です。

「[Go言語でスヌーズ機能付きリマインダーLINE botを実装した](https://qiita.com/kawasin73/items/c8d0fc7ede6036e5ce39)」で作ったLINE bot のサーバーを heroku で運用していたのですが毎月700円の出費が痛いので、なんとか安い運用方法がないかを検討した結果、自宅で眠っていた `Mac mini` を自宅サーバーとして運用することにしました。

## 動機と OS の選択

- お金をかけずに運用したい。
- Docker が好きなので kubernetes を触ってみたい

この2つが僕のマストの要求です。

まず思い浮かぶのが `GCP` の無料枠での運用ですが、GKE を無料枠の `f1-micro` インスタンスを1つ立ち上げて試してみたところ kubernetes の管理アプリケーションだけで、`0.6GB` のメモリのほとんどを食い尽くし、アプリケーションのデプロイは望めなさそうだったので、クラウドでの運用は諦めました。
参考: [貧者の GKE / 無料枠だけでクラスタを作ろう](https://qiita.com/apstndb/items/788f705e71e7660967a6)

1年ほど前に自宅サーバーで遊ぶためにメルカリで２万円で買って、すぐ飽きて放置していた `Mac mini` があったのでそれを使うことにしました。

kubernetes を動かすためには Linux が必要ですが、自動で OS をアップデートしてくれて、Docker を動かす以外のことをしない最小の構成でセキュリティのリスクが少ない `CoreOS` を採用しました。継続的な運用の手間をなるべくかけたくなかったのでその負荷を下げてくれることを期待しています。

Mac mini での運用では、Mac mini の中に `VM` を `Vagrant` などで立てて運用している記事を多く見かけましたが、Mac のホストのポートを VM のポートにいちいちフォワーディングしないといけないのがめんどくさいので、ディスクにインストールして起動する方式にしました。

また、いざという時は macOS を使いたいので OS は、macOS と CoreOS のデュアルブートをすることにしました。

## kubernetes の立ち上げ方

kubernetes の立ち上げは、`Rancher` と `kubeadm` を試して `kubeadm` を採用することにしました。`kubeadm` はまだベータ版ですが、kubernetes のオフィシャルで作られており、シンプルでいい感じだったので採用しました。

Rancher は、マシンの `80番ポート` を管理ページのために占有されてしまうため採用を見送りました。管理ページは https での閲覧しかできないのですが、SSL証明書を `let's encrypt` を利用して取得するオプションを用意しています。しかしそのためには、マシンの `80番ポート` をインターネットに公開する必要があり、Rancher の管理プロセスと kubernetes は同じマシンで動かすため、kubernetes で動かすサービスが 80番ポート を使うことができなくなります。kubernetes 上のサービスで let's encrypt を利用することができなくなってしまうため、採用を見送りました。

## 固定IP 問題

自宅は  DHCP により**動的にパブリックIPアドレスが割り当て**られ、ルーターの再起動などで IPアドレス が変わってしまう可能性があります。運用中のサービスのIPアドレスが変わったら、即サービスダウンです。

解決策としては、以下の３通りが考えられます。

- 固定IP を割り当てる
- DDNS (`Dynamic DNS`) を利用する
- ルーターの設定されたIPアドレスを監視し、変更されたら DNS のレコードを更新する

固定IP を割り当てる場合は、プロバイダーによるとは思いますが、申請が必要でさらに有料です。お金がかかるので今回は採用しませんでした。

DDNS を利用する方法は、自分が使っているソフトバンク光から支給されたルーターが対応していないため断念しました。普通のバッファローのルーターなどでは対応しているみたいなので、ルーターを変えるという選択肢もあります。しかしソフトバンク光は、支給されたルーター以外では IPv6 での通信をさせない仕様になっており、IPv6 が使えないと普段使いに支障をきたすレベルでネットが遅くなるので断念しました。

消去法的に、ルーターから `パブリックIPアドレス` をなんらかの方法で取得するのを一定時間で取得して、DNS のレコードを更新する方針を取ることになりました。ルーターからIPアドレスをぶっこ抜くAPIなどは提供されていないのですが、ソフトバンク光から支給された [EWMTA2.1](http://ybb.softbank.jp/support/connect/hikari/router/bbu2-setupmenu.php) の設定管理画面の HTML と javascript を眺めていると、 `http://172.16.255.254/GetWanIP.html` で**パブリックIPアドレスをぶっこぬけることを発見**し、利用することにしました。

[`wanpoll`](https://github.com/kawasin73/wanpoll) という、一定間隔で管理ページから IPアドレス をぶっこぬいて監視し、変更されたら Route53 のDNSレコードを更新する簡単なツールをGoで作成しました。詳しくは、https://github.com/kawasin73/wanpoll のgithubページを参照してください。`wanpoll` 自体は、HTTPで取得したテキストの中で最初に現れる `IPv4` のアドレスを正規表現でぶっこぬきます。ソフトバンク光のルーター以外でもルーターの管理ページに IPv4 アドレスを一つだけ掲載しているページがあれば利用できます。

## インストールと起動と試行錯誤

インストールに成功するまでに色々試行錯誤しました。この章では、その試行錯誤を紹介します。実際の手順は次の章で説明します。

### 試行錯誤1 : macOS で `coreos-install` を行う。

CoreOS のディスクへのインストールは、`coreos-install` という「[こちら](https://raw.githubusercontent.com/coreos/init/master/bin/coreos-install)」のBashスクリプトが用意されています。

しかし、このスクリプトは Linux で実行することが前提とされているため、 **macOS では動きません！！！！！！** `lsblk` というマウントされたディスクを調べるコマンドが macOS にはないため実行時にエラーになります。

`coreos-install` 自体はただの Bash スクリプトで、やってることは `環境のチェック` と、`OSイメージのダウンロード`、`ディスクへの dd での書き込み` だけなので、書き換えれば macOS でも動かすことはできるとは思いますが、そこまでの気力はなかったので macOS でインストールスクリプトを実行させることは諦めました。

参考URL: [Installing CoreOS Container Linux to disk](https://coreos.com/os/docs/latest/installing-to-disk.html)

### 試行錯誤2 : パーティショニングした Mac mini の HDD にインストールする

それであればと、転がっていた 8GB のUSBメモリに「[このページ](https://coreos.com/os/docs/latest/booting-with-iso.html)」からダウンロードした ISO イメージを書き込んで rEFInd で CoreOS を起動し、パーティショニングした Mac mini の HDD に対して、`coreos-install` を実行してみましたが、以下のエラーが出てインストールに失敗しました。

```
$0: Target block device (${DEVICE}) is not a full disk.
```

パーティショニングされているディスクへの CoreOS のインストールはできず、インストールするときはディスクを丸々使うようです。Mac mini のディスクを全て消去して CoreOS を入れるのは怖かったので、HDD にインストールすることは諦めました。

参考URL: [Booting CoreOS Container Linux from an ISO](https://coreos.com/os/docs/latest/booting-with-iso.html)

### 試行錯誤3 : Docker for Mac で USBメモリ に対して `coreos-install` を行う

そのため、家に転がっていた `32GB` の USBメモリ に CoreOS をインストールして、USB OS として起動することにしました。

それであるなら、Mac mini 1台で作業を完結させたいと思い、Mac mini 上の `Docker for Mac` で Linux を実行して CoreOS のインストールをしようと考えました。しかし、中古の Mac mini では homebrew でインストールした `Docker for Mac` が、CPU のバージョンが古いというエラーメッセージを吐いて起動できませんでした。

やむをえず、Macbook Pro で動いている `Docker for Mac` で Linux を実行してインストールすることにしました。[この記事](https://docs.sevenbridges.com/docs/mount-a-usb-drive-in-a-docker-container) を参考に、USBメモリを Docker コンテナ上にマウントして、`coreos-install` を実行しましたが、`coreos-install` はディスクが `/dev/` ディレクトリ上にマウントされていることを要求するため、インストールに失敗しました。macOS の上にある、Docker VM へのメモリのマウント方法を模索するのはめんどくさいですし、`Docker for Mac` の環境を壊したくなかったので、`Docker for Mac` で CoreOS をインストールすることは諦めました。

参考記事: [Mount a USB drive in a Docker container](https://docs.sevenbridges.com/docs/mount-a-usb-drive-in-a-docker-container)

### 試行錯誤4: USBメモリにインストールして、rEFInd でデュアルブートする

USBメモリ に CoreOS のISOイメージをコピーして起動し、別のUSBメモリに CoreOS をインストールして rEFInd を使ってデュアルブートする方法で CoreOS のインストールができましたが、運用して数日でサーバーが落ちるという事象にぶち当たりました。

ディスプレイを繋げてみるとなぜか macOS のログイン画面が表示されます。これは、CoreOS の自動OSアップデートによって再起動されたものの、rEFInd によって長時間レスポンスがなかったため自動で macOS が優先的に起動されてしまったものだと考えられます。調べてみると、運用をしていた数日の間に CoreOS の新しいバージョンがリリースされていました。

そのため、rEFInd の起動の優先度を変更することで対応しました。

## 最終的に採用した CoreOS のインストール方法

紆余曲折をへて、ディスクに macOS を残したまま、CoreOS を USBメモリ にインストールし、rEFInd でデュアルブートする方式を取ることにしました。

CoreOS は　[`Ignition`](https://coreos.com/ignition/docs/latest/) という仕組みを使うことで、OSのインストール時に設定した JSON 設定ファイルの内容を、起動時に実行して OS の設定をすることができます。設定ファイルは、`yaml` 形式で書き、それを [Container Linux Config Transpiler](https://github.com/coreos/container-linux-config-transpiler) というツールを使って JSON ファイルに変換して使います。

設定ファイルは `8GB` のUSBメモリの CoreOS から、DropBox に一時的に公開した設定ファイルを `curl` コマンドでダウンロードして用意するようにしました。JSON ファイルの内容を全てキーボードで叩く気にはなれないですし、設定ファイルには秘匿情報が含まれているため、インターネットへの公開もできません。また、CoreOS のまっさらなイメージには、`curl` と `wget` くらいしかダウンロードコマンドがなく、S3 のAPIを使ったりするのは、コマンドのダウンロードと認証情報の入力が必要になり、とてもめんどくさいです。https に対応して、Basic 認証でファイルのダウンロードができる簡単な無料サービスがあると `curl` コマンドで安全にダウンロードできて便利なのですが、そんなサービスを知らなかったので Dropbox を使うことにしました。

では、いかに手順をまとめていきます。

### 0. ルーターの設定

事前に自宅ルーターの設定を行います。

- LAN 内のプライベートIPアドレスの固定アドレスの割り振り
    - 固定IP である方が扱いやすいため、プライベートIP の固定アドレスの設定を行いました。
- 外部からのアクセスのポートフォワーディング
    - 僕は、`80` 番と `443` 番を Mac mini に転送するように設定しました。

### 1. Mac mini への rEFInd のインストール

[`rEFInd`](http://www.rodsbooks.com/refind/) は、デュアルブートを簡単にGUIから行えるようにするソフトウェアです。El capitan 以降の macOS を使っている場合は、System Integrity Protection(SIP) を無効化してから出ないとインストールできないため注意が必要です。

1. Mac mini をシャットダウンした後に、`Command + R` を押しながら起動しリカバリーモードに入ります。
2. ユーティリティからターミナルを立ち上げ、以下のコマンドを実行して SIP を無効化します。

    ```bash
    csrutil disable
    ```

3. Mac mini をシャットダウンした後に通常モードで起動して、rEFInd の [ダウンロードページ](http://www.rodsbooks.com/refind/getting.html) の、`A binary zip file` リンクをクリックして、`zip` ファイルをダウンロードします。
4. `zip` ファイルを解凍して、内包されていた `refind-install` コマンドを実行します。これで、rEFInd がインストールされました。

    ```bash
    cd ~/Downloads/refind-bin-0.11.3/
    ./refind-install
    ```

5. 最後に、Mac mini をシャットダウンした後に再度リカバリーモードで起動し、ターミナルで以下のコマンドを実行することで、SIP を有効化します。

    ```bash
    csrutil enable
    ```

参考URL: [El capitanにrEFIndを導入するとき](https://qiita.com/yuki_bg/items/61ecf0b8a2d84823c365)

### 2. rEFInd の優先度の変更

rEFInd は、デフォルトの状態では、起動して操作をせずに 20 秒経つと自動で macOS のディスクを起動します。このままでは、CoreOS の自動OSアップデートが実行されて再起動されると macOS が立ち上がってしまうことになります。そのため、rEFInd の設定を変更して、最後に起動したディスクが優先的に起動されるように変更します。

起動ディスクの優先度は、設定ファイルの `default_selection` で設定できます。これに、`+` を指定すると最後に起動したディスクが優先的に起動されます。

1. macOS を起動してターミナルを起動します。
2. EFI ディスクの場所を調べます。この場合は、`/dev/disk0s1` です。

```bash
$ diskutil list
/dev/disk0 (internal, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *500.3 GB   disk0
   1:                        EFI EFI                     209.7 MB   disk0s1
   2:          Apple_CoreStorage Macintosh HD            499.4 GB   disk0s2
   3:                 Apple_Boot Recovery HD             650.0 MB   disk0s3

/dev/disk1 (internal, virtual):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:                  Apple_HFS Macintosh HD           +499.1 GB   disk1
                                 Logical Volume on disk0s2
                                 A3EFDCC4-52BE-4DA6-8AD2-E47995C8C94F
                                 Unlocked Encrypted
```

3. EFI ディスクをマウントします。ここでは、`/Volumes/EFI` にマウントします。

```bash
sudo mkdir /Volumes/EFI
sudo diskutil mount -mountPoint /Volumes/EFI /dev/disk0s1
```

4. `/Volumes/EFI/EFI/refind/refind.conf` を修正します。

```bash
$ vi /Volumes/EFI/EFI/refind/refind.conf
# 以下を追加
# default_selection +
```

### 3. 暫定USBメモリへの CoreOS ISOイメージの書き込み

次に、`coreos-install` を実行する Linux 環境を手に入れるために、`8GB` のUSBメモリへ CoreOS の ISOイメージ を焼き付けます。Linux 環境があればいいので、CoreOS 以外でも可能ですし、メモリの容量もイメージが入りきれば指定はありません。

1. まず、USBメモリを初期化します。macOS のデフォルトでインストールされている `ディスクユーティリティ` を開き、指した USB メモリを選択して `消去` ボタンをクリックします。![ディスクユーティリティ.png](https://qiita-image-store.s3.amazonaws.com/0/65193/70045721-9237-76a2-edeb-29cecb4a11f7.png)
2. 名前を適当に入力し、フォーマットに `MS-DOS(FAT)` を選択して `消去` ボタンをクリックします。これで USBメモリ の消去が完了しました。
3. この [ダウンロードページ](https://coreos.com/os/docs/latest/booting-with-iso.html) から CoreOS の ISOイメージをダウンロードします。
4. ISO イメージを `dd` コマンドを使ってコピーして USBOS の完成です。詳しくは、[参考URL](http://hal0taso.hateblo.jp/entry/2016/09/08/190140) をみてください。ただ、このサイトでは、`bs` を `4028` に設定していますが、これだとすごい時間がかかるので、`32m` くらいに設定して実行します。

    ```bash
    # disk4 に USBメモリがあるとする。
    diskutil unmountDisk /dev/disk4
    sudo dd if=./Downloads/coreos_production_iso_image.iso of=/dev/disk4 bs=32m
    diskutil eject /dev/disk4
    ```

参考URL: [Mac OSX上でISOイメージからBootable USBを作成する](http://hal0taso.hateblo.jp/entry/2016/09/08/190140)

### 4. Ignition ファイルの用意

Ignition は、起動時に CoreOS の設定をする仕組みです。yaml ファイルで設定しますが、このテンプレートは、[この記事](https://vadosware.io/post/yet-another-cluster-reinstall-back-to-container-linux/) を参考にして作りました。

追加したテンプレートに追加した点としては、SSH の設定と、`wanpoll` の設定です。利用するときは、`ssh_authorized_keys` を自分の PC の ssh の公開鍵に変更し、`/opt/wanpoll/env` の内容を書き換えてください。

yaml ファイルは、[Container Linux Config Transpiler](https://github.com/coreos/container-linux-config-transpiler) を使って、JSON にトランスパイルします。

```bash
brew install coreos-ct
ct -out-file ignition.json -platform custom < ignition.yml
```

Container Linux Config のテンプレート yaml ファイルは以下の通りです。

```yml
# FROM URL: https://vadosware.io/post/yet-another-cluster-reinstall-back-to-container-linux/
# This config is meant to be consumed by the config transpiler, which will
# generate the corresponding Ignition config. Do not pass this config directly
# to instances of Container Linux.

# NOTE: This configuration is meant to work with Config Transpiler v0.8.0
# The spec is available at (https://github.com/coreos/container-linux-config-transpiler/blob/v0.8.0/doc/configuration.md)

passwd:
  users:
    - name: core
      ssh_authorized_keys:
        - ssh-rsa xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxx@example.com

systemd:
  units:
    # Docker will be configured initially but we'll be using containerd exclusively and will disable it after containerd setup
    - name: docker.service
      enabled: true

    # containerd without docker as a shim, thanks to containerd.service.d/ overrides
    - name: containerd.service
      enabled: true

    - name: sshd.socket
      dropins:
      - name: 10-sshd-port.conf
        contents: |
          [Socket]
          ListenStream=
          ListenStream=2222

    - name: k8s-install.service
      enabled: true
      contents: |
        [Install]
        WantedBy=multi-user.target

        [Unit]
        Description=k8s installation script
        Wants=network-online.target
        After=network.target network-online.target

        [Service]
        Type=oneshot
        ExecStart=/ignition/init/k8s/install.sh

    - name: cni-install.service
      enabled: true
      contents: |
        [Install]
        WantedBy=multi-user.target

        [Unit]
        Description=cni plugin installation script
        Requires=k8s-install.service
        After=k8s-install.service

        [Service]
        Type=oneshot
        ExecStart=/ignition/init/cni/install.sh

    - name: containerd-install.service
      enabled: true
      contents: |
        [Install]
        WantedBy=multi-user.target

        [Unit]
        Description=containerd installation script
        Requires=cni-install.service
        After=cni-install.service

        [Service]
        Type=oneshot
        ExecStart=/ignition/init/cri-containerd/install.sh

    - name: kubeadm-install.service
      enabled: true
      contents: |
        [Install]
        WantedBy=multi-user.target

        [Unit]
        Description=kubeadm installation script
        Requires=containerd-install.service
        After=containerd-install.service

        [Service]
        Type=oneshot
        Environment="PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/opt/bin"
        ExecStart=/ignition/init/kubeadm/kubeadm-install.sh

    - name: k8s-setup.service
      enabled: true
      contents: |
        [Install]
        WantedBy=multi-user.target

        [Unit]
        Description=kubernetes setup script
        Requires=kubeadm-install.service
        After=kubeadm-install.service

        [Service]
        Type=oneshot
        User=core
        Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/opt/bin"
        ExecStart=/ignition/init/k8s/setup.sh

    - name: wanpoll.service
      enabled: true
      contents: |
        [Install]
        WantedBy=multi-user.target

        [Unit]
        Description=wanpoll service
        Documentation=https://github.com/kawasin73/wanpoll

        [Service]
        Type=idle
        EnvironmentFile=/opt/wanpoll/env
        ExecStart=/opt/bin/wanpoll -hz=${HZ} \
          -name=${NAME} \
          -user=${USER} \
          -password=${PASSWORD} \
          -interval=${INTERVAL} \
          -ttl=${INTERVAL}

storage:
  filesystems:
    - mount:
        device: /dev/disk/by-label/ROOT
        format: xfs
        wipe_filesystem: true
        label: ROOT

  files:
    - path: /opt/bin/kubeadm
      filesystem: root
      mode: 493 # 0755
      contents:
        remote:
          url: https://storage.googleapis.com/kubernetes-release/release/v1.10.2/bin/linux/amd64/kubeadm

    - path: /opt/bin/kubelet
      filesystem: root
      mode: 493 # 0755
      contents:
        remote:
          url: https://storage.googleapis.com/kubernetes-release/release/v1.10.2/bin/linux/amd64/kubelet

    - path: /opt/bin/kubectl
      filesystem: root
      mode: 511 # 0777
      contents:
        remote:
          url: https://storage.googleapis.com/kubernetes-release/release/v1.10.2/bin/linux/amd64/kubectl

    - path: /opt/bin/wanpoll
      filesystem: root
      mode: 0755
      contents:
        remote:
          url: https://github.com/kawasin73/wanpoll/releases/download/v0.1.0/wanpoll-linux-amd64

    - path: /etc/systemd/system/kubelet.service
      filesystem: root
      mode: 420 # 0644
      contents:
        remote:
          url: https://raw.githubusercontent.com/kubernetes/kubernetes/v1.10.2/build/debs/kubelet.service

    - path: /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
      filesystem: root
      mode: 420 # 0644
      contents:
        remote:
          url: https://raw.githubusercontent.com/kubernetes/kubernetes/v1.10.2/build/debs/10-kubeadm.conf

    - path: /ignition/init/cri-containerd/cri-containerd-1.1.0.linux-amd64.tar.gz
      filesystem: root
      mode: 420 # 0644
      contents:
        remote:
          url: https://storage.googleapis.com/cri-containerd-release/cri-containerd-1.1.0.linux-amd64.tar.gz

    - path: /ignition/init/cni/cni-plugins-v0.7.1.tgz
      filesystem: root
      mode: 420 # 0644
      contents:
        remote:
          url: https://github.com/containernetworking/plugins/releases/download/v0.7.1/cni-plugins-amd64-v0.7.1.tgz

    - path: /ignition/init/canal/rbac.yaml
      filesystem: root
      mode: 493 # 0755
      contents:
        remote:
          url: https://docs.projectcalico.org/v3.1/getting-started/kubernetes/installation/hosted/canal/rbac.yaml

    - path: /ignition/init/canal/canal.yaml
      filesystem: root
      mode: 493 # 0755
      contents:
        remote:
          url: https://docs.projectcalico.org/v3.1/getting-started/kubernetes/installation/hosted/canal/canal.yaml

    - path: /opt/wanpoll/env
      filesystem: root
      mode: 0600
      contents:
        inline: |
          USER=xxxxxxxxxxxxx
          PASSWORD=xxxxxxxxxxxx
          INTERVAL=5
          NAME=xxxxx.example.com,xxxxx.example.com
          HZ=ZZZZZZZZZZZZ
          AWS_ACCESS_KEY_ID=XXXXXXXXXXXXX
          AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    - path: /ignition/init/k8s/install.sh
      filesystem: root
      mode: 480 # 740
      contents:
        inline: |
          #!/bin/bash

          # Unzip the kubernetes binaries if not already present
          test -d /opt/bin/kubeadm && echo "k8s binaries (kubeadm) already installed" && exit 0

          # NOTE: If RELEASE is updated, the SHA512 SUMs will need to be as well
          echo -e "=> Installing k8s v1.10.2"

          echo "=> Cusomizing kubelet.service..."
          sed -i "s:/usr/bin:/opt/bin:g" /etc/systemd/system/kubelet.service
          sed -i "s:/usr/bin:/opt/bin:g" /etc/systemd/system/kubelet.service.d/10-kubeadm.conf

          systemctl daemon-reload
          systemctl enable kubelet
          systemctl start kubelet

    - filesystem: root
      path: /ignition/init/cri-containerd/install.sh
      mode: 480 # 740
      contents:
        inline: |
          #!/bin/bash

          # Unzip the kubernetes binaries if not already present
          test -d /opt/containerd && echo "containerd binaries already installed" && exit 0

          VERSION=1.1.0
          echo -e "=> Installing containerd v${VERSION}"

          echo "=> Installing containerd...."
          cd /ignition/init/cri-containerd
          tar -C / -k -xzf cri-containerd-${VERSION}.linux-amd64.tar.gz

          echo "=> Copying /usr/local binaries to /opt/bin ...."
          mkdir -p /ignition/init/cri-containerd/unzipped
          tar -C unzipped -k -xzf cri-containerd-${VERSION}.linux-amd64.tar.gz
          cp -r unzipped/usr/local/bin/* /opt/bin
          systemctl start containerd

          echo "=> Adding dropins...."
          cat > /etc/systemd/system/kubelet.service.d/0-containerd.conf <<EOF
          [Service]
          Environment="KUBELET_EXTRA_ARGS=--container-runtime=remote --runtime-request-timeout=15m --container-runtime-endpoint=unix:///run/containerd/containerd.sock --volume-plugin-dir=/var/lib/kubelet/volumeplugins"
          EOF

          mkdir -p /etc/systemd/system/containerd.service.d/
          cat > /etc/systemd/system/containerd.service.d/0-direct-containerd.conf <<EOF
          [Service]
          ExecStart=
          ExecStart=/opt/bin/containerd
          EOF

          echo "=> Triggering systemctl daemon-reload...."
          systemctl daemon-reload
          systemctl restart containerd

    - filesystem: root
      path: /ignition/init/cni/install.sh
      mode: 480 # 740
      contents:
        inline: |
          #!/bin/bash

          # Unzip the kubernetes binaries if not already present
          test -d /opt/cni/bin && echo "CNI binaries already installed" && exit 0

          VERSION=0.7.1
          echo -e "=> Installing CNI (v${VERSION}) binaries to /opt/cni/bin"
          cd /ignition/init/cni
          mkdir -p /opt/cni/bin
          tar -C /opt/cni/bin -k -xzf cni-plugins-v${VERSION}.tgz

    - filesystem: root
      path: /ignition/init/kubeadm/kubeadm-install.sh
      mode: 480 # 740
      contents:
        inline: |
          #!/bin/bash

          # Ensure kubeadm binary is present
          test -f /opt/bin/kubeadm || (echo "Failed to find kubeadm binary" && exit 1)

          # Exit if kubeadm has already been run (/etc/kubernetes folder would have been created)
          test -d /etc/kubernetes && echo "/etc/kubernetes is present, kubeadm should have already been run once" && exit 0

          echo "=> Running kubeadm init..."
          /opt/bin/kubeadm init --cri-socket "/run/containerd/containerd.sock" --pod-network-cidr "10.244.0.0/16"

          # Disable docker (kubelet will use containerd runtime directly)
          sudo systemctl stop docker
          sudo systemctl disable docker

          echo "=> Running kubeadm post-install set up for user 'core'"
          mkdir -p /home/core/.kube
          cp -i /etc/kubernetes/admin.conf /home/core/.kube/config
          chown $(id -u core):$(id -g core) /home/core/.kube/config

    - filesystem: root
      path: /ignition/init/k8s/setup.sh
      mode: 493 # 0755
      contents:
        inline: |
          #!/bin/bash

          # Ensure /etc/kubernetes is present (created by kubeadm)
          test -d /etc/kubernetes || (echo "/etc/kubernetes not present, ensure kubeadm has run properly" && exit 1)

          echo "=> Enabling workload running on the master node"
          kubectl taint nodes --all node-role.kubernetes.io/master-

          echo "=> Installing canal"
          kubectl apply -f /ignition/init/canal/rbac.yaml
          kubectl apply -f /ignition/init/canal/canal.yaml

    - path: /etc/ssh/sshd_config
      filesystem: root
      mode: 0600
      contents:
        inline: |
          # Use most defaults for sshd configuration.
          UsePrivilegeSeparation sandbox
          Subsystem sftp internal-sftp
          UseDNS no

          PasswordAuthentication no
          PermitRootLogin no
          AllowUsers core
          AuthenticationMethods publickey
```

参考URL: [YET ANOTHER CLUSTER RE-INSTALL AFTER SWITCHING BACK TO CONTAINER LINUX](https://vadosware.io/post/yet-another-cluster-reinstall-back-to-container-linux/)

### 5. 本番USBメモリへの CoreOS のインストール

ここまできたらようやく本番の USB メモリへ CoreOS のインストールができます。

1. 本番用の USBメモリ を初期化します。初期化は、`暫定USBメモリへの CoreOS ISOイメージの書き込み` の時と同じ手順で中身を消去します。今回は、`32GB` の USBメモリ を使いました。
2. 前段階で作成した `ignition.json` を Dropbox にアップロードし、共有リンクを作成して控えておきます。
3. Mac mini を終了して起動すると、rEFInd による OS の選択画面が現れます。USB メモリの CoreOS を選びましょう。確か、Custom イメージみたいな感じで表示されたような気がしますが、それが CoreOS です。
3. CoreOS が起動します。起動するまでには時間がかかりますが少し待ちます。
4. 起動に成功したら、Dropbox から `ignition.json` をダウンロードします。CoreOS には `curl` と `wget` がインストールされているのでどちらかを使ってダウンロードします。
5. `lsblk` コマンドを実行して、USBメモリがマウントされている場所を特定します。ここでは、`/dev/sbc` とします。
6. `coreos-install` を実行して CoreOS をインストールします。CoreOS の場合はすでに用意されているので以下のコマンドを実行します。

    ```bash
    coreos-install -d /dev/sbc -i ignition.json -C stable
    ```

7. CoreOS 以外の Linux の場合は、以下のコマンドでインストールします。

    ```bash
    curl https://raw.githubusercontent.com/coreos/init/master/bin/coreos-install | bash -s -- -d /dev/sbc -i ignition.json -C stable
    ```

8. 以上でインストールは完了です。CoreOS を終了しましょう。

    ```bash
    shutdown
    ```

### 6. 起動

インストールが成功したら、起動をします。`8GB` の USBメモリ は必要ないので、Mac mini から抜いておきます。

Mac mini を起動すると、rEFInd で起動ディスクを選びます。なぜか CoreOS の起動ディスクが4つくらい表示されるのですが、左のほうのペンギンのアイコンを選択すると起動できます。

バイナリのダウンロードや kubernetes のセットアップが実行されるため起動には時間がかかりますが、待っていれば起動に成功します。

別の端末から、ssh でログインしてログインできることを確かめてください。

## まとめ

長くなりましたが、CoreOS + kubernetes の環境の立ち上げができました。

CoreOS の Ignition の設定はあまり深く練っていないのでアドバイスなどがあれば教えていただきたいです。

次回は、kubernetes にアプリケーションをデプロイし、複数サービスが同時に運用できるようにプロキシの設定をしていきます。