MLOpsを実現するKubeflowを前提のKubernetesも含めて構築する(前編)

初版: 2021/4/9
著者: 高久 隆史, 株式会社日立製作所

# はじめに
[Kubeflow](https://www.kubeflow.org/)はKubernetes上でMLOpsを実現するためのOSSのツールキットです。2020/3にバージョン1.0がリリースされ、2021/4現在も活発に開発が行われており、機械学習を利用したシステムの開発/運用のライフサイクルを回すための有効な手段の１つとして注目されています。

本連載では、Kubeflowのバージョン1.2(連載開始時点の最新版、2020年11月リリース)について、構築手順、Kubeflow Pipelines、KFServingの基礎的な利用手順についての情報を紹介いたします。

第1回の本稿では、Amazon Web Services(以降AWSと記載します) EC2上で、OSのみインストールされている環境から、Kubeflowを前提のKubernetesも含めて構築する手順を記載いたします。
本稿で紹介する構築手順は、AWS EC2上だけでなく、オンプレミスで構築する場合も流用できる手順となっていますので、是非参考にしてください。

なお、構築手順は長くなるため、途中まで記載し、続きは、[第2回の投稿](https://qiita.com/t-takahisa/items/c481cdb188096eb29289)で記載いたします。


投稿一覧：
1. MLOpsを実現するKubeflowを前提のKubernetesも含めて構築する(前編) (本投稿)
2. [MLOpsを実現するKubeflowを前提のKubernetesも含めて構築する(後編)](https://qiita.com/t-takahisa/items/c481cdb188096eb29289)
3. [Kubeflow Pipelinesでscikit-learnの機械学習モデルを訓練・評価してみる](https://qiita.com/t-takahisa/items/0a409478cb502f4d3c5f)
4. [Kubeflow KFServingでscikit-learnの学習済みモデルを用いた推論サービスを公開する](https://qiita.com/a-yanagimura/items/2fd5a7cb51ced76ed4c6)


# Kubeflowの構築パターンと最小スペック
Kubeflowは、オンプレミス、パブリッククラウドのどちらでも構築できます。公式サイトに記載されている構築パターンを次に示します。

**Kubeflowの構築パターン**

<table>
<col width="3%">
<col width="36%">
<col width="10%">
<col width="15%">
<col width="36%">
 <tr>
  <th>#</th>
  <th>分類</th>
  <th>OS</th>
  <th>Kubernetes</th>
  <th>構築手順のURL</th>
 </tr>
 <tr>
  <td>1</td>
  <td>任意のKubernetesクラスタにKubeflowをデプロイ</td>
  <td>-</td>
  <td>任意</td>
  <td><a href="https://www.kubeflow.org/docs/started/k8s/">http://www.kubeflow.org/docs/started/k8s/</a></td>
 </tr>
 <tr>
  <td>2</td>
  <td rowspan="10">ワークステーションにシングルノードKubernetesクラスタを構築してKubeflowをデプロイ</td>
  <td rowspan="4">Linux</td>
  <td>MicroK8s</td>
  <td rowspan="4"><a href="https://www.kubeflow.org/docs/started/workstation/getting-started-linux/">https://www.kubeflow.org/docs/started/workstation/getting-started-linux/</a></td>
 </tr>
 <tr>
  <td>3</td>
  <td>MiniKF</td>
 </tr>
 <tr>
  <td>4</td>
  <td>Minikube</td>
 </tr>
 <tr>
  <td>5</td>
  <td>Multipass</td>
 </tr>
 <tr>
  <td>6</td>
  <td rowspan="3">Windows</td>
  <td>MicroK8s</td>
  <td rowspan="3"><a href="https://www.kubeflow.org/docs/started/workstation/getting-started-windows/">https://www.kubeflow.org/docs/started/workstation/getting-started-windows/</a></td>
 </tr>
 <tr>
  <td>7</td>
  <td>MiniKF</td>
 </tr>
 <tr>
  <td>8</td>
  <td>Multipass</td>
 </tr>
 <tr>
  <td>9</td>
  <td rowspan="3">macOS</td>
  <td>MicroK8s</td>
  <td rowspan="3"><a href="https://www.kubeflow.org/docs/started/workstation/getting-started-macos/">https://www.kubeflow.org/docs/started/workstation/getting-started-macos/</a></td>
 </tr>
 <tr>
  <td>10</td>
  <td>MiniKF</td>
 </tr>
 <tr>
  <td>11</td>
  <td>Multipass</td>
 </tr>
 <tr>
  <td>12</td>
  <td rowspan="4">パブリッククラウドのKubernetes環境にKubeflowをデプロイ</td>
  <td>-</td>
  <td>AWS EKS</td>
  <td><a href="https://www.kubeflow.org/docs/aws/deploy/">https://www.kubeflow.org/docs/aws/deploy/</a></td>
 </tr>
 <tr>
  <td>13</td>
  <td>-</td>
  <td>Microsoft Azure AKS</td>
  <td><a href="https://www.kubeflow.org/docs/azure/deploy/">https://www.kubeflow.org/docs/azure/deploy/</a></td>
 </tr>
 <tr>
  <td>14</td>
  <td>-</td>
  <td>Google Cloud Platform GKE</td>
  <td><a href="https://www.kubeflow.org/docs/gke/deploy/">https://www.kubeflow.org/docs/gke/deploy/</a></td>
 </tr>
 <tr>
  <td>15</td>
  <td>-</td>
  <td>IBM IKS</td>
  <td><a href="https://www.kubeflow.org/docs/ibm/deploy/">https://www.kubeflow.org/docs/ibm/deploy/</a></td>
 </tr>
 <tr>
  <td>16</td>
  <td>パブリッククラウドのKubernetes環境にKubeflowをデプロイ</td>
  <td>-</td>
  <td>OpenShift</td>
  <td><a href="https://www.kubeflow.org/docs/openshift/">https://www.kubeflow.org/docs/openshift/</a></td>
 </tr>
</table>

Kubeflowの公式サイトで確認できた、Kubeflow構築のための最小スペックを次に示します。

**Kubeflow構築のための最小スペック**

<table>
 <col width="3%">
 <col width="30%">
 <col width="9%">
 <col width="8%">
 <col width="18%">
 <col width="32%">
 <tr>
  <th rowspan="2">#</th>
  <th rowspan="2">構築パターン</th>
  <th colspan="3">最小スペック</th>
  <th rowspan="2">参考URL</th>
 </tr>
 <tr>
  <td>CPU</td>
  <td>RAM</td>
  <td>ストレージ</td>
 </tr>
 <tr>
  <td>1</td>
  <td>KubernetesクラスタにKubeflowをデプロイ</td>
  <td>4コア</td>
  <td>12GB</td>
  <td>50GB</td>
  <td><a href="https://www.kubeflow.org/docs/started/k8s/overview/">https://www.kubeflow.org/docs/started/k8s/overview/</a></td>
 </tr>
 <tr>
  <td>2</td>
  <td>Minikubeを使用</td>
  <td>8コア</td>
  <td>16GB</td>
  <td>250GB</td>
  <td><a href="https://www.kubeflow.org/docs/started/workstation/minikube-linux/">https://www.kubeflow.org/docs/started/workstation/minikube-linux/</a></td>
 </tr>
</table>


# 構築時の留意事項
Kubeflowの構築時に問題が発生した場合、Webサイトなどで調べても原因が分からず、苦労することがあります。Kubeflowの環境構築を行う上での留意点を次に示しますので参考にしてください。

**Kubeflowの環境構築を行う上での留意点**

| # | 留意点 | 理由 |
| :- | :- | :- |
| 1 | スペックに余裕のあるマシンを利用する。 | リソース不足で予期しないエラーが発生して構築や実行ができない可能性があるため。 |
| 2 | プロキシのないネットワーク環境を利用する。 | 構築時にインターネットアクセスが発生するが、プロキシ設定を行っていても、その設定が効かずにエラーとなって正しく構築できない可能性があるため。 |
| 3 | Docker Hub Pull数制限に引っ掛からないようにする(必要に応じて無料アカウントの登録などを検討する)。 | 構築時にDocker pullが発生するため。 |
| 4 | KubernetesとOSの組合せに注意する。 | ・kubeadmやMicroK8sを利用する場合、OSがRHEL8やCentOS8では(iptables-legacyに対応していないため)動作しない。<br/>・Minikubeを利用する場合は、OSはUbuntuのみに対応していると思われる。公式サイトの「Kubeflow on Windows」や「Kubeflow on macOS」のページにもMinikubeの記載があるが、その先のページはUbuntu前提となっていたため。また、実際にWindowsで構築してみようとしたが、kubectlのWindows用媒体がなく、構築できなかった。 |


# 構築の前提情報
本稿では、OSのみインストールされている環境に、KubernetesおよびKubeflow環境を新規に構築するときの手順を記載します。
具体的には、Kubeflowの前提となるKubernetesはkubeadmを利用して構築し、そこにKubeflowをデプロイする形で構築します。それぞれ以下の公式サイトの手順に沿って構築します。
https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
http://www.kubeflow.org/docs/started/k8s/

本稿の構築手順は、特定のパブリッククラウドなどに限定せずに、オンプレミスでも構築できる手順となりますが、OSの種類、Kubeflowのバージョン、その他の初期設定などによって、手順が異なる場合がありますので、留意願います。
本書の構築手順で利用したマシンの環境情報、および、インストールしたソフトウェアとそのバージョン情報を次に示します。

**本構築手順で利用したマシンの環境情報**

| # | 項目 | 内容 |
| :- | :- | :- |
| 1 | マシン概要 | AWS EC2　(1インスタンス) |
| 2 | インスタンスタイプ | t3a.2xlarge |
| 3 | VCPU | 4 |
| 4 | RAM | 32GiB |
| 5 | ストレージ | 192GB |
| 6 | OS | Ubuntu 18.04 LTS - Bionic |
| 7 | ポート | SSH(22)、および、以下のkubeadmが利用するポートは解放しておく。<br/>https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#check-required-ports |

**本構築手順でインストールしたソフトウェアとそのバージョン情報**

<table>
 <col width="3%">
 <col width="3%">
 <col width="25%">
 <col width="25%">
 <col width="44%">
 <tr>
  <th>#</th>
  <th colspan="2">ソフトウェア</th>
  <th>バージョン</th>
  <th>補足</th>
 </tr>
 <tr>
  <td>1</td>
  <td colspan="2">Docker CE</td>
  <td>19.03.11</td>
  <td>kubeadm前提のコンテナランタイムとして選択。</td>
 </tr>
 <tr>
  <td>2</td>
  <td colspan="2">Kubernetes</td>
  <td>1.20.1</td>
  <td rowspan="4">Kubeflow前提のKubernetesとして選択。</td>
 </tr>
 <tr>
  <td>3</td>
  <td rowspan="3" />
  <td>kubeadm</td>
  <td>1.20.1-00 amd64</td>
 </tr>
 <tr>
  <td>4</td>
  <td>kubectl</td>
  <td>1.20.1-00 amd64</td>
 </tr>
 <tr>
  <td>5</td>
  <td>kubelet</td>
  <td>1.20.1-00 amd64</td>
 </tr>
 <tr>
  <td>6</td>
  <td colspan="2">Flannel</td>
  <td>v0.11.0</td>
  <td>kubeadm前提のPodネットワークアドオンとして選択。</td>
 </tr>
 <tr>
  <td>7</td>
  <td colspan="2">Kubernetes Dashboard</td>
  <td>v2.0.0</td>
  <td>必須ではないが、状態確認用途として構築。</td>
 </tr>
 <tr>
  <td>8</td>
  <td colspan="2">Local-path-provisioner</td>
  <td>v0.0.19</td>
  <td>Kubeflow前提のDynamic Volume Provisionerとして選択。</td>
 </tr>
 <tr>
  <td>9</td>
  <td colspan="2">Kubeflow</td>
  <td>1.2.0</td>
  <td rowspan="2" />
 </tr>
 <tr>
  <td>10</td>
  <td />
  <td>kfctl</td>
  <td>v1.2.0-0-gbc038f9</td>
 </tr>
</table>


# 構築の流れ
今回記載する構築の一連の流れを次に記載します。

**構築の流れ**

| # | 作業項目 | 概要 |
| :- | :- | :- |
| 1  | SSHでログイン | 構築対象マシンにSSHでログインします。 |
| 2  | Swapの無効化 | kubeadmの前提の設定作業として、Swapを無効化します。 |
| 3  | iptablesにブリッジトラフィックを表示 | kubeadmの前提の設定作業として、iptablesにブリッジトラフィックを表示します。 |
| 4  | コンテナラインタイムのインストール | kubeadmの前提の設定作業として、コンテナランタイムをインストールします。 |
| 5  | kubeadm、kubelet、kubectlのインストール | Kubeflow前提のKubernetesとしてkubeadmおよびkubeadmの関連ソフトウェアであるkubelet、kubectlをインストールします。 |
| 6  | kubeadmを使用したシングルコントロールプレーンクラスターの作成 | kubeadmの設定作業として、シングルコントロールプレーンクラスターを作成します。 |
| 7  | Podネットワークアドオンのインストール | kubeadmのPod同士の通信のため、Podネットワークアドオンのインストールを行います。 |
| 8  | コントロールプレーンノードの隔離 | kubeadmを、1台のクラスタで利用するため、コントロールプレーンノードの隔離の設定を行います。 |
| 9  | Kubernetes Dashboard のデプロイ、アクセス（任意） | 状態確認などをブラウザから行いたい場合は、Kubernetes Dashboardのデプロイを実施します。本手順は任意となります。 |
| 10 | Dynamic Volume Provisionerのインストール | Kubeflowの前提の設定作業として、Dynamic Volume Provisionerをインストールします。 |
| 11 | Kubeflowのデプロイ | Kubeflowのインストール作業として、KubernetesにKubeflowをデプロイします。 |
| 12 | Kubeflow Central Dashboardへのアクセス | デプロイしたKubeflowにKubeflow Central Dashboardからアクセスできることを確認します。 |
| 13 | Notebook Serverの作成、接続 | Kubeflow Central DashboardからNotebook Serverを作成し、接続します。 |


# 構築手順
第1回の本稿では、上で記載した「構築の流れ」の#9まで(Kubeflowの前提Kubernetesとするkubeadmの構築まで)の手順を記載します。#10以降の手順は[第2回の投稿](https://qiita.com/t-takahisa/items/c481cdb188096eb29289)を参照してください。

## 1. SSHでログイン
構築するマシンにSSHでログインします。


## 2. Swapの無効化
kubeadmの前提として、Swapが無効化されている必要があるため、Swapを無効化します。
https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#before-you-begin

```shell
# Swapが無効になっているか確認
free
# --- 実行結果例 ここから -----------------------------------------------------------
#               total        used        free      shared  buff/cache   available
# Mem:       32613620      243620    30588784         768     1781216    31908148
# Swap:             0           0           0
# --- 実行結果例 ここまで -----------------------------------------------------------

# 今回は、Swapが0になっており、すでにSwapは無効でしたが、有効となっている場合は、
# 「sudo swapoff -a」コマンドの実行、および、「/etc/fstab」で定義されている
# Swapの設定をコメントアウトすることで無効化できます。
```


## 3. iptablesにブリッジトラフィックを表示
iptablesにブリッジトラフィックを表示させます。
https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#letting-iptables-see-bridged-traffic

```shell
# br_netfilterモジュールがロードされているか確認
lsmod | grep br_netfilter
# --- 実行結果例 ここから -----------------------------------------------------------
# (何も表示されない)
# --- 実行結果例 ここまで -----------------------------------------------------------

# (ロードされていない場合) br_netfilterモジュールをロードする
sudo modprobe br_netfilter

# ロードされていることを確認
lsmod | grep br_netfilter
# --- 実行結果例 ここから -----------------------------------------------------------
# br_netfilter           24576  0
# bridge                155648  1 br_netfilter
# --- 実行結果例 ここまで -----------------------------------------------------------

# iptablesがブリッジを追加するトラフィックを処理できるようにカーネルパラメータを変更
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF

# カーネルパラメータを反映
sudo sysctl --system
```


## 4. コンテナラインタイムのインストール
コンテナを実行するランタイムとしてDockerをインストールします。
(他にcontainerdやCRI-Oもありますが、Kubeflow Pipelinesに必要なArgoはDockerでのみ動作するため、本手順ではDockerをインストールします。)
https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#installing-runtime
https://kubernetes.io/docs/setup/production-environment/container-runtimes/

```shell
# Dockerのインストール
# https://docs.docker.com/engine/install/ubuntu/
# https://kubernetes.io/docs/setup/production-environment/container-runtimes/#docker

# HTTPS経由でリポジトリを使用できるようにする
sudo apt-get update
sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common

# Dockerの公式GPGキーを追加
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
# --- 実行結果例 ここから -----------------------------------------------------------
# OK
# --- 実行結果例 ここまで -----------------------------------------------------------

# Dockerのリポジトリを設定(安定版)
sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"

# Docker Engineとcontainerdをインストール
sudo apt-get update
sudo apt-get install -y \
  containerd.io=1.2.13-2 \
  docker-ce=5:19.03.11~3-0~ubuntu-$(lsb_release -cs) \
  docker-ce-cli=5:19.03.11~3-0~ubuntu-$(lsb_release -cs)

# DockerEngineが正しくインストールされていることを確認
sudo docker run hello-world
# --- 実行結果例 ここから -----------------------------------------------------------
# Unable to find image 'hello-world:latest' locally
# latest: Pulling from library/hello-world
# 0e03bdcc26d7: Pull complete
# Digest: sha256:1a523af650137b8accdaed439c17d684df61ee4d74feac151b5b337bd29e7eec
# Status: Downloaded newer image for hello-world:latest
# 
# Hello from Docker!
# This message shows that your installation appears to be working correctly.
# 
# To generate this message, Docker took the following steps:
#  1. The Docker client contacted the Docker daemon.
#  2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
#     (amd64)
#  3. The Docker daemon created a new container from that image which runs the
#     executable that produces the output you are currently reading.
#  4. The Docker daemon streamed that output to the Docker client, which sent it
#     to your terminal.
# 
# To try something more ambitious, you can run an Ubuntu container with:
#  $ docker run -it ubuntu bash
# 
# Share images, automate workflows, and more with a free Docker ID:
#  https://hub.docker.com/
# 
# For more examples and ideas, visit:
#  https://docs.docker.com/get-started/
# --- 実行結果例 ここまで -----------------------------------------------------------

# Dockerデーモンを設定
cat <<EOF | sudo tee /etc/docker/daemon.json
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2"
}
EOF

# /etc/systemd/system/docker.service.d ディレクトリを作成
sudo mkdir -p /etc/systemd/system/docker.service.d

# Dockerをリスタート
sudo systemctl daemon-reload
sudo systemctl restart docker

# Dockerサービスを有効化
sudo systemctl enable docker
# --- 実行結果例 ここから -----------------------------------------------------------
# Synchronizing state of docker.service with SysV service script with /lib/systemd/systemd-sysv-install.
# Executing: /lib/systemd/systemd-sysv-install enable docker
# --- 実行結果例 ここまで -----------------------------------------------------------
```

## 5. kubeadm、kubelet、kubectlのインストール
kubeadm、kubelet、kubectlをインストールします。
https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#installing-kubeadm-kubelet-and-kubectl

```shell
# 前提パッケージをインストール
sudo apt-get update
sudo apt-get install -y apt-transport-https curl

# Googleの公式GPG keyを追加
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
# --- 実行結果例 ここから -----------------------------------------------------------
# OK
# --- 実行結果例 ここまで -----------------------------------------------------------

# GoogleのKubernetesのリポジトリを設定
cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF

# Kubernetesの関連パッケージをインストール
sudo apt-get update
sudo apt-get install -y kubelet=1.20.1-00 kubeadm=1.20.1-00 kubectl=1.20.1-00

sudo apt-mark hold kubelet kubeadm kubectl
# --- 実行結果例 ここから -----------------------------------------------------------
# kubelet set on hold.
# kubeadm set on hold.
# kubectl set on hold.
# --- 実行結果例 ここまで -----------------------------------------------------------
```


## 6. kubeadmを使用したシングルコントロールプレーンクラスターの作成
kubeadmを使用したシングルコントロールプレーンクラスターを作成します。
https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/
https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/

```shell
# gcr.ioコンテナイメージレジストリに接続できるかどうかを確認
sudo kubeadm config images pull
# --- 実行結果例 ここから -----------------------------------------------------------
# [config/images] Pulled k8s.gcr.io/kube-apiserver:v1.20.1
# [config/images] Pulled k8s.gcr.io/kube-controller-manager:v1.20.1
# [config/images] Pulled k8s.gcr.io/kube-scheduler:v1.20.1
# [config/images] Pulled k8s.gcr.io/kube-proxy:v1.20.1
# [config/images] Pulled k8s.gcr.io/pause:3.2
# [config/images] Pulled k8s.gcr.io/etcd:3.4.13-0
# [config/images] Pulled k8s.gcr.io/coredns:1.7.0
# --- 実行結果例 ここまで -----------------------------------------------------------

# Kubelet初期化用の設定ファイルを作成(PodネットワークアドオンにFlannelを使用するのに必要な設定を記載)
cat <<EOF > kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
networking:
  podSubnet: "10.244.0.0/16" # --pod-network-cidr
EOF

# 上記作成したファイルを使用して、コントロールプレーンノード(Master)を初期化
sudo kubeadm init --config kubeadm-config.yaml
# --- 実行結果例 ここから -----------------------------------------------------------
# [init] Using Kubernetes version: v1.20.1
# [preflight] Running pre-flight checks
# [preflight] Pulling images required for setting up a Kubernetes cluster
# [preflight] This might take a minute or two, depending on the speed of your internet connection
# [preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
# [certs] Using certificateDir folder "/etc/kubernetes/pki"
# [certs] Generating "ca" certificate and key
# [certs] Generating "apiserver" certificate and key
# [certs] apiserver serving cert is signed for DNS names [ip-x-x-x-x kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 x.x.x.x]
# [certs] Generating "apiserver-kubelet-client" certificate and key
# [certs] Generating "front-proxy-ca" certificate and key
# [certs] Generating "front-proxy-client" certificate and key
# [certs] Generating "etcd/ca" certificate and key
# [certs] Generating "etcd/server" certificate and key
# [certs] etcd/server serving cert is signed for DNS names [ip-x-x-x-x localhost] and IPs [x.x.x.x 127.0.0.1 ::1]
# [certs] Generating "etcd/peer" certificate and key
# [certs] etcd/peer serving cert is signed for DNS names [ip-x-x-x-x localhost] and IPs [x.x.x.x 127.0.0.1 ::1]
# [certs] Generating "etcd/healthcheck-client" certificate and key
# [certs] Generating "apiserver-etcd-client" certificate and key
# [certs] Generating "sa" key and public key
# [kubeconfig] Using kubeconfig folder "/etc/kubernetes"
# [kubeconfig] Writing "admin.conf" kubeconfig file
# [kubeconfig] Writing "kubelet.conf" kubeconfig file
# [kubeconfig] Writing "controller-manager.conf" kubeconfig file
# [kubeconfig] Writing "scheduler.conf" kubeconfig file
# [kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
# [kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
# [kubelet-start] Starting the kubelet
# [control-plane] Using manifest folder "/etc/kubernetes/manifests"
# [control-plane] Creating static Pod manifest for "kube-apiserver"
# [control-plane] Creating static Pod manifest for "kube-controller-manager"
# [control-plane] Creating static Pod manifest for "kube-scheduler"
# [etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
# [wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
# [apiclient] All control plane components are healthy after 8.506934 seconds
# [upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
# [kubelet] Creating a ConfigMap "kubelet-config-1.20" in namespace kube-system with the configuration for the kubelets in the cluster
# [upload-certs] Skipping phase. Please see --upload-certs
# [mark-control-plane] Marking the node ip-x-x-x-x as control-plane by adding the labels "node-role.kubernetes.io/master=''" and "node-role.kubernetes.io/control-plane='' (deprecated)"
# [mark-control-plane] Marking the node ip-x-x-x-x as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]
# [bootstrap-token] Using token: iexug3.l1dd4ka1szzuzpmr
# [bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
# [bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to get nodes
# [bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
# [bootstrap-token] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
# [bootstrap-token] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
# [bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
# [kubelet-finalize] Updating "/etc/kubernetes/kubelet.conf" to point to a rotatable kubelet client certificate and key
# [addons] Applied essential addon: CoreDNS
# [addons] Applied essential addon: kube-proxy
# 
# Your Kubernetes control-plane has initialized successfully!
# 
# To start using your cluster, you need to run the following as a regular user:
# 
#   mkdir -p $HOME/.kube
#   sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
#   sudo chown $(id -u):$(id -g) $HOME/.kube/config
# 
# Alternatively, if you are the root user, you can run:
# 
#   export KUBECONFIG=/etc/kubernetes/admin.conf
# 
# You should now deploy a pod network to the cluster.
# Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
#   https://kubernetes.io/docs/concepts/cluster-administration/addons/
# 
# Then you can join any number of worker nodes by running the following on each as root:
# 
# kubeadm join x.x.x.x:6443 --token iexug3.l1dd4ka1szzuzpmr \
#     --discovery-token-ca-cert-hash sha256:xxxxx
# --- 実行結果例 ここまで -----------------------------------------------------------

# kubectlの設定ファイルをユーザ領域にコピー
mkdir $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
```


## 7. Podネットワークアドオンのインストール
Podネットワークアドオンとして、Flannelをインストールします。
https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/#pod-network
https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/#pod-network


```shell
# Flannelをインストール
sudo kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/2140ac876ef134e0ed5af15c65e414cf26827915/Documentation/kube-flannel.yml
# --- 実行結果例 ここから -----------------------------------------------------------
# podsecuritypolicy.policy/psp.flannel.unprivileged created
# Warning: rbac.authorization.k8s.io/v1beta1 ClusterRole is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 ClusterRole
# clusterrole.rbac.authorization.k8s.io/flannel created
# Warning: rbac.authorization.k8s.io/v1beta1 ClusterRoleBinding is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 ClusterRoleBinding
# clusterrolebinding.rbac.authorization.k8s.io/flannel created
# serviceaccount/flannel created
# configmap/kube-flannel-cfg created
# daemonset.apps/kube-flannel-ds-amd64 created
# daemonset.apps/kube-flannel-ds-arm64 created
# daemonset.apps/kube-flannel-ds-arm created
# daemonset.apps/kube-flannel-ds-ppc64le created
# daemonset.apps/kube-flannel-ds-s390x created
# --- 実行結果例 ここまで -----------------------------------------------------------

# Podが起動するまで少し待つ(数十秒～数分くらい)

# Podネットワークが動作していることを確認。CoreDNS PodがRunning状態であればOK
kubectl get pods --all-namespaces
# --- 実行結果例 ここから -----------------------------------------------------------
# NAMESPACE     NAME                                  READY   STATUS    RESTARTS   AGE
# kube-system   coredns-74ff55c5b-fnvg8               1/1     Running   0          3m48s
# kube-system   coredns-74ff55c5b-zw8nq               1/1     Running   0          3m48s
# kube-system   etcd-ip-x-x-x-x                      1/1     Running   0          4m1s
# kube-system   kube-apiserver-ip-x-x-x-x            1/1     Running   0          4m1s
# kube-system   kube-controller-manager-ip-x-x-x-x   1/1     Running   0          4m1s
# kube-system   kube-flannel-ds-amd64-wxln5           1/1     Running   0          45s
# kube-system   kube-proxy-qd5n2                      1/1     Running   0          3m48s
# kube-system   kube-scheduler-ip-x-x-x-x            1/1     Running   0          4m1s
# --- 実行結果例 ここまで -----------------------------------------------------------
```


## 8. コントロールプレーンノードの隔離
デフォルトでは、クラスタはコントロールプレーンノードにPodをスケジューリングしません。本手順では、1台のクラスタでPodをコントロールプレーンノードにスケジューリングしたいため、その設定を行います。
https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/#control-plane-node-isolation

```shell
sudo kubectl taint nodes --all node-role.kubernetes.io/master-
# --- 実行結果例 ここから -----------------------------------------------------------
# node/ip-x-x-x-x untainted
# --- 実行結果例 ここまで -----------------------------------------------------------

# クラスタのノード一覧とIPアドレスを確認
kubectl get nodes
# --- 実行結果例 ここから -----------------------------------------------------------
# NAME          STATUS   ROLES                  AGE   VERSION
# ip-x-x-x-x   Ready    control-plane,master   43m   v1.20.1
# --- 実行結果例 ここまで -----------------------------------------------------------

# Service一覧とPortを確認
kubectl get services -A
# --- 実行結果例 ここから -----------------------------------------------------------
# NAMESPACE     NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
# default       kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP                  44m
# kube-system   kube-dns     ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP,9153/TCP   44m
# --- 実行結果例 ここまで -----------------------------------------------------------

# Pod一覧を確認
kubectl get pods --all-namespaces
# --- 実行結果例 ここから -----------------------------------------------------------
# NAMESPACE     NAME                                  READY   STATUS    RESTARTS   AGE
# kube-system   coredns-74ff55c5b-fnvg8               1/1     Running   0          44m
# kube-system   coredns-74ff55c5b-zw8nq               1/1     Running   0          44m
# kube-system   etcd-ip-x-x-x-x                      1/1     Running   0          44m
# kube-system   kube-apiserver-ip-x-x-x-x            1/1     Running   0          44m
# kube-system   kube-controller-manager-ip-x-x-x-x   1/1     Running   0          44m
# kube-system   kube-flannel-ds-amd64-wxln5           1/1     Running   0          41m
# kube-system   kube-proxy-qd5n2                      1/1     Running   0          44m
# kube-system   kube-scheduler-ip-x-x-x-x            1/1     Running   0          44m
# --- 実行結果例 ここまで -----------------------------------------------------------
```


## 9. Kubernetes Dashboard のデプロイ、アクセス（任意）
※本手順は任意です。Kubernetes Dashboardを利用しない場合はスキップしてください。

Kubernetes Dashboardのデプロイを行います。
https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/

```shell
# DashboardのYAMLを取得
wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0/aio/deploy/recommended.yaml

# 取得したファイルを編集(行頭に+がついている行を追加)し、外部からアクセス可能にする
vi recommended.yaml
# --- ファイル編集例 ここから -------------------------------------------------------
#   kind: Service
#   apiVersion: v1
#   metadata:
#     labels:
#       k8s-app: kubernetes-dashboard
#     name: kubernetes-dashboard
#     namespace: kubernetes-dashboard
#   spec:
# +   type: NodePort
#     ports:
#       - port: 443
#         targetPort: 8443
# +       nodePort: 30843
#     selector:
#       k8s-app: kubernetes-dashboard
# --- ファイル編集例 ここまで -------------------------------------------------------

# デプロイを実行
kubectl apply -f recommended.yaml
# --- 実行結果例 ここから -----------------------------------------------------------
# namespace/kubernetes-dashboard created
# serviceaccount/kubernetes-dashboard created
# service/kubernetes-dashboard created
# secret/kubernetes-dashboard-certs created
# secret/kubernetes-dashboard-csrf created
# secret/kubernetes-dashboard-key-holder created
# configmap/kubernetes-dashboard-settings created
# role.rbac.authorization.k8s.io/kubernetes-dashboard created
# clusterrole.rbac.authorization.k8s.io/kubernetes-dashboard created
# rolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
# clusterrolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
# deployment.apps/kubernetes-dashboard created
# service/dashboard-metrics-scraper created
# deployment.apps/dashboard-metrics-scraper created
# --- 実行結果例 ここまで -----------------------------------------------------------

# Serviceの一覧からkubernetes-dashboardのPort 30843を確認
kubectl get services -A
# --- 実行結果例 ここから -----------------------------------------------------------
# NAMESPACE              NAME                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                  AGE
# default                kubernetes                  ClusterIP   10.96.0.1        <none>        443/TCP                  47m
# kube-system            kube-dns                    ClusterIP   10.96.0.10       <none>        53/UDP,53/TCP,9153/TCP   47m
# kubernetes-dashboard   dashboard-metrics-scraper   ClusterIP   10.108.221.215   <none>        8000/TCP                 37s
# kubernetes-dashboard          NodePort    10.104.134.142   <none>        443:30843/TCP            37s
# --- 実行結果例 ここまで -----------------------------------------------------------

# IPを確認
kubectl get nodes -o wide
# --- 実行結果例 ここから -----------------------------------------------------------
# NAME          STATUS   ROLES                  AGE   VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION   CONTAINER-RUNTIME
# ip-x-x-x-x   Ready    control-plane,master   48m   v1.20.1   x.x.x.x      <none>        Ubuntu 18.04.5 LTS   5.4.0-1029-aws   docker://19.3.11
# --- 実行結果例 ここまで -----------------------------------------------------------

# アカウントを作成
# https://github.com/kubernetes/dashboard/blob/master/docs/user/access-control/creating-sample-user.md
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kubernetes-dashboard
EOF
# --- 実行結果例 ここから -----------------------------------------------------------
# serviceaccount/admin-user created
# --- 実行結果例 ここまで -----------------------------------------------------------

# ClusterRoleBindingを作成
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kubernetes-dashboard
EOF
# --- 実行結果例 ここから -----------------------------------------------------------
# clusterrolebinding.rbac.authorization.k8s.io/admin-user created
# --- 実行結果例 ここまで -----------------------------------------------------------

# ログイン用トークンを取得
kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep admin-user | awk '{print $1}')
# --- 実行結果例 ここから -----------------------------------------------------------
# Name:         admin-user-token-pbcnr
# Namespace:    kubernetes-dashboard
# Labels:       <none>
# Annotations:  kubernetes.io/service-account.name: admin-user
#               kubernetes.io/service-account.uid: e29f699a-ea03-4084-8eec-75e116086d31
# 
# Type:  kubernetes.io/service-account-token
# 
# Data
# ====
# ca.crt:     1066 bytes
# namespace:  20 bytes
# token:      xxxxx
# --- 実行結果例 ここまで -----------------------------------------------------------

# ステータスを確認(Runningになるまで待ってから次の手順に進むこと)
kubectl get pods -n kubernetes-dashboard
# --- 実行結果例 ここから -----------------------------------------------------------
# NAME                                         READY   STATUS    RESTARTS   AGE
# dashboard-metrics-scraper-7b59f7d4df-jbskk   1/1     Running   0          3m21s
# kubernetes-dashboard-74d688b6bc-v5f8z        1/1     Running   0          3m21s
# --- 実行結果例 ここまで -----------------------------------------------------------
```

* ブラウザからKubernetes Dashboardにアクセスします。<br/>
https://\<構築したマシンのIPアドレス\>:30843/

**Kubernetes Dashboard トークン入力画面**
![Kubernetes_Dashboard_input_token.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/624448/aec22b4d-f7de-7d9a-f760-c2a5c338f53d.png)

* 取得したトークンを入力して、「サインイン」をクリックします。

**Kubernetes Dashboard トップページ**
![Kubernetes_Dashboard_top.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/624448/a2d73ac0-77c6-4e19-69c3-d9df401337b8.png)


# おわりに
本稿では、MLOpsを実現するKubeflowを前提のKubernetesも含めて構築する手順として、前提Kubernetesの構築までを紹介しました。次回は、残りの構築手順を紹介します。

[第2回：MLOpsを実現するKubeflowを前提のKubernetesも含めて構築する(後編)](https://qiita.com/t-takahisa/items/c481cdb188096eb29289)