kube-vipを用いたHA Kubernetes Clusterの構築
- 注意事項
- はじめに
- Kubernetes Clusterの構築
- K8s Cluster上でのDeepLearning環境の構築
- 確認
- おわりに
注意事項
この記事の内容は一部誤り(kube-apiserver回り)があることがわかっており,動作はしますが非常に不安定です.そのため参考程度にしてください.(2020/08/22)
はじめに
去年の記事でシングルMaster構成のKubernetes(以下K8s) Clusterを作成しました. tenzen.hatenablog.com
シングルマスター構成は細かい事を気にせず構築できて便利なのですが,実際にサービスとして稼働させる際はMasterの冗長化も行いたいところです.
そこで今回はHigh Availability(HA)構成のK8s Clusterを構築し, その上にDeepLearning環境を構築していきます. Qiitaやブログをみると,KeepAlivedとHAProxyをK8s Cluster外の別ノードで構築し,Masterの役割をロードバランシングしている例が拝見されます.しかし今回は以下のドキュメントに準拠しつつ,kube-vip(Kube-vip)を用いてMasterを冗長化していきます.
ここでK8sの役割名についてですが,公式ドキュメントに準拠し,本節以降HA構成をとったK8s Clusterにおいてetcdやapi-serverなどが載っているNodeをcontrol plane node,それ以外をnodeと呼びます.
また,以前の記事「Kubernetes(k8s) v1.16とNvidia-Docker2を用いたマルチノードDeepLearning環境の構築 - てんぜんの生存日誌」や「Rook-Cephfs + Nvidia Driver vs てんぜん - てんぜんの生存日誌」と 同じ作業部分も記述しているので,わかっているorもうやっている人が読みとばせるように一つ目の記事と同じ部分は節名に「(以前と同様#1)」,二つ目の記事と同じ部分は説明に「(以前と同様#2)」と記述しているので活用してください.
※別Nodeでetcd Clusterを構築する構成もありますが,本記事ではetcdをcontrol plane nodeに載せた構成を構築します.
Kubernetes Clusterの構築
今回構築するK8s環境の物理構成は以下の通りになります.本節以降はNode名が頻繁に出てきますので,適宜参照してください. また,今回は記事執筆時点で最新版のK8s,v1.18.3を使用します.
ホスト名 | 役割 | CPU | メモリ | GPU | OS |
---|---|---|---|---|---|
k8s-m0 | control plane + node | intel Core i5 9600K | 64GB(Non ECC) | なし | Ubuntu 16.04LTS(linux-image-4.15.0-72-generic) |
k8s-m1 | control plane + node | intel Core i5 9600K | 64GB(Non ECC) | なし | Ubuntu 16.04LTS(linux-image-4.15.0-72-generic) |
michiru | control plane + node | intelXeon Gold 6230 ×2 | 192GB(ECC Registerd) | Quadro GP100 ×4 | Ubuntu 16.04LTS(linux-image-4.15.0-72-generic) |
utaha | node | intel Core i9 9900K | 128GB(Non ECC) | RTX2080Ti×2 | Ubuntu 16.04LTS(linux-image-4.15.0-72-generic) |
eriri | node | intel Core i9 9900K | 128GB(Non ECC) | RTX2080Ti×2 | Ubuntu 16.04LTS(linux-image-4.15.0-72-generic) |
Step01[全Node共通事項]準備
この章では各種ソフトウェアをインストールする前の準備を行います. 明記しない限り全てのnode,control plane nodeで同じ設定を行ってください.
linux カーネルバージョンの変更(以前と同様#2)
以下の記事で説明した経緯でlinux kernelのバージョンをあげます.(ubuntu 18.04など使用する場合は関係ありません.) tenzen.hatenablog.com
まず現在のKernel Imageを調べておきます.
$ sudo uname -a Linux tenzen 4.4.0-170-generic #199-Ubuntu SMP Thu Nov 14 01:45:04 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
次に現在使用できるKernel Imageを探してきます.
$ sudo apt-cache search linux-image | grep image-4 | grep generic (省略) linux-image-4.15.0-54-generic - Signed kernel image generic linux-image-4.15.0-55-generic - Signed kernel image generic linux-image-4.15.0-58-generic - Signed kernel image generic linux-image-4.15.0-60-generic - Signed kernel image generic linux-image-4.15.0-62-generic - Signed kernel image generic linux-image-4.15.0-64-generic - Signed kernel image generic linux-image-4.15.0-65-generic - Signed kernel image generic linux-image-4.15.0-66-generic - Signed kernel image generic linux-image-4.15.0-69-generic - Signed kernel image generic linux-image-4.15.0-70-generic - Signed kernel image generic linux-image-4.15.0-72-generic - Signed kernel image generic linux-image-4.15.0-74-generic - Signed kernel image generic linux-image-4.15.0-76-generic - Signed kernel image generic linux-image-4.15.0-88-generic - Signed kernel image generic linux-image-4.15.0-91-generic - Signed kernel image generic linux-image-4.15.0-96-generic - Signed kernel image generic linux-image-4.15.0-99-generic - Signed kernel image generic linux-image-4.4.0-101-generic - Linux kernel image for version 4.4.0 on 64 bit x86 SMP linux-image-4.4.0-103-generic - Linux kernel image for version 4.4.0 on 64 bit x86 SMP ...(省略)
今回はlinux-image-4.15.0-72-genericを使用していきます
$ sudo apt-get install -y linux-image-4.15.0-72-generic $ reboot
再起動すると一番新しいKernel Imageを選択するようになっているので切り替わっていると思います.
$ sudo uname -a Linux k8s-m 4.15.0-72-generic #81~16.04.1-Ubuntu SMP Tue Nov 26 16:34:21 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
スワップ機能のオフ(以前と同様#1)
K8sは,スワップ機能に対応していないためスワップ機能をオフにする必要があります.
そこで以下のコマンドを実行してスワップ機能のオンオフを確認してください.
$ cat /etc/fstab # /etc/fstab: static file system information. # # Use 'blkid' to print the universally unique identifier for a # device; this may be used with UUID= as a more robust way to name devices # that works even if disks are added and removed. See fstab(5). # # <file system> <mount point> <type> <options> <dump> <pass> # / was on /dev/sda2 during installation UUID=hoge hoge hoge / ext4 errors=remount-ro 0 1 # /boot/efi was on /dev/sda1 during installation UUID=hoge hoge /boot/efi vfat umask=0077 0 1 swap was on /dev/sda3 during installation #UUID=hoge hoge hoge none swap sw 0 0
上記のように「swap was on ~」の部分がコメントアウトされていない場合は,スワップ機能がオンになっているためコメントアウトしてスワップ機能をオフにしてください.その後以下のコマンドを実行してスワップがオフになっているか確認してください.
$ swapon -s
何か表示された場合は以下のコマンドを実行してスワップを削除します.
$ swapoff -a
ポートの解放
次に,kubernetesが使用するポートを開放していきます.
開放しなければならないポートはKubernetesの公式インストールガイドにも書いてあります.
今回はufwを用いてポート制御を行うためufwをオンにし,確認を行ってください.
$ sudo ufw enable $ sudo ufw status Status: active
次に,Kubernetes公式インストールガイドにしたがって下記のポートを開けていきます.
また今回はflannelを用いて内部ネットワークを構築するため,以下のように他のポートも開けていきます.他にもkube-vipで通過うポートなど以下ポート番号表にあるポートを解放します.
Node | ポート番号 |
---|---|
control plane node | 53, 6443-6444, 2379-2380, 8285, 8472, 9153, 10000, 10250-10252 |
node | 53, 8285, 8472, 9153, 10250, 30000-32767 |
controls plane node
$ sudo ufw allow 53 &&\ sudo ufw allow 6443-6444 &&\ sudo ufw allow 2379:2380/tcp &&\ sudo ufw allow 8285 &&\ sudo ufw allow 8472 &&\ sudo ufw allow 9153 &&\ sudo ufw allow 10000 &&\ sudo ufw allow 10250:10252/tcp
node
$ sudo ufw allow 53 &&\ sudo ufw allow 6444 &&\ sudo ufw allow 8285 &&\ sudo ufw allow 8472 &&\ sudo ufw allow 9153 &&\ sudo ufw allow 10000 &&\ sudo ufw allow 10250 &&\ sudo ufw allow 30000:32767/tcp
name serverの変更(以前と同様)
最後にdnsmasqが悪さをしてgithubのissuesのようにKubernetesのCoreDNSを壊したり, github.com
コンテナ内から通信できなくなったりするためname serverを変更します.
まず,以下のコマンドを実行して設定変更の必要があるかどうかを確認します.
$ cat /etc/resolv.conf # Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) # DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN nameserver 127.0.1.1
上記のようにnameserverが「127.0.1.1」になっている場合は設定変更の必要があります.それ以外の場合は以下の作業を行わなくても大丈夫なはずです.
まず下記のコマンドでNetworkManager.confを表示させ,「dns=dnsmasq」の部分がコメントアウトになっているか確認してください. コメントアウトされていない場合は「dns=dnsmasq」の部分をコメントアウトしてください.
その後ネットワークマネージャを再起動し,resolve.confを再確認してnameserverが変わっていることを確認してください.その際127.0.1.1の設定が残っている場合コメントアウトするか,削除してください.
$ cat /etc/NetworkManager/NetworkManager.conf [main] plugins=ifupdown,keyfile,ofono dns=dnsmasq [ifupdown] managed=false $ sudo systemctl restart network-manager $ cat /etc/resolv.conf # Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) # DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN nameserver xxx.xxx.xxx.xxx
以上で準備は完了です.
Step02[全Node共通事項]:ソフトウェアのインストール
この章では各種ソフトウェアのインストールを行います. 明記しない限り全てのcontrol plane node,nodeで同じ設定を行ってください.
Nvidia Driver(以前と同様#2)
今回は最新のNvidia Driverの中でも比較的安定しているnvidia-440をインストールしました.
Nvidia Driverはcontrol plane nodeにのみインストールしてください.(control plane nodeにもGPUを積んでnodeとして使用する場合はcontrol plane nodeにもインストールしてください.)
まずはじめにNvidia DriverのインストーラはLinux Headerを見てインストールするものを判断しているため,使用中のLinux Imageに該当するLinux Headerをインストールします.
$ sudo apt-get install -y linux-headers-4.15.0-72-generic $ reboot
再起動後「/usr/src/」にlinux-headers-4.15.0-72-genericがあることを確認します.これがないとNvidia Driverを正常にインストールすることができませんのでよく確認しておいてください.
$ ls /usr/src linux-headers-4.15.0-72-generic ...(省略)
Nvidia公式サイトから自分が使用中のGPUなどを選択してインストーラをダウンロードてきます.今回は440番を使用しました.
ダウンロードできたらダウンロードしてきたファイルの権限を変更後ディスプレイマネージャを切ってCUIモードでログインし直してください.(UbuntuならCtr+Alt+F1)
$ mv /home/tenzen/Downloads/NVIDIA-Linux-x86_64-440.44.run /home/tenzen $ chmod 777 NVIDIA-Linux-x86_64-440.44.run $ sudo service lightdm stop
CUIモードでログインできたらドライバをインストールしていきます.インストーラを起動すると青い画面になって色々聞かれるので使用しているホストに合わせて答えていってください.
$ sudo NVIDIA-Linux-x86_64-440.44.run
Docker(以前と同様#1)
次にDocker18.06.3をインストールします. 以下のコマンドを順番に実行していけばインストールできます. 途中,「sudo apt-cache policy docker-ce」コマンドの出力結果に「docker-ce=18.06.3~ce~3-0~ubuntu」があることを確認してください.
$ sudo apt-get install apt-transport-https ca-certificates curl software-properties-common $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - $ sudo apt-key fingerprint 0EBFCD88 $ sudo add-apt-repository \ "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) \ stable" $ sudo apt-get update $ sudo apt-cache policy docker-ce $ sudo apt-get install -y docker-ce=18.06.3~ce~3-0~ubuntu
ここで,指定バージョンのdockerがインストールできたかの確認をしてPC起動時にdockerが起動するよう設定しておきます.
$ docker -v Docker version 18.06.3-ce, build d7080c1 $ service docker start && service docker status
Nvidia-Docker2
さらにNvidia-Docker2のインストールをしていきます.
Nvidia-Docker2はcontrol plane nodeにのみインストールしてください.(control plane nodeにもGPUを積んでいる場合はcontrol plane nodeにもインストールしてください.)
よく似た名前でNvidia-Dockerと言うものがありますが,別物なので間違ってインストールしないように気をつけてください. また,docker v19.03以降からdockerがgpuにネイティブ対応したためNvidia-Docker2ではなくnvidia-container-toolkitを使用するようになりました. しかしながら公式ドキュメント GitHub - NVIDIA/k8s-device-plugin: NVIDIA device plugin for Kubernetes にあるように記事執筆時点でnvidia-container-toolkitには対応しておらずNvidia-docker2を使用するようにアナウンスされているので注意してください.
Note that you need to install the nvidia-docker2 package and not the nvidia-container-toolkit. This is because the new --gpus options hasn't reached kubernetes yet.
以下のコマンドを順番に実行していきます.
$ curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - $ curl -s -L https://nvidia.github.io/nvidia-docker/ubuntu16.04/amd64/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list $ sudo apt-get update $ sudo apt-get install -y nvidia-docker2 $ sudo pkill -SIGHUP dockerd
これでNvidia-docker2のインストールは完了しました.
インストールが完了したら,nvidia-docker2のバージョン確認とdockerのランタイムをnvidia-docker2のものへ変更します.
まず,以下のコマンドでnvidia-docker2のバージョンを確認し,nvidia-dockerではなくnvidia-docker2がインストールできているか確認します.
$ sudo nvidia-docker version NVIDIA Docker: 2.2.2 Client: Version: 18.06.3-ce API version: 1.38 Go version: go1.10.3 Git commit: d7080c1 Built: Wed Feb 20 02:27:18 2019 OS/Arch: linux/amd64 Experimental: false Server: Engine: Version: 18.06.3-ce API version: 1.38 (minimum version 1.12) Go version: go1.10.3 Git commit: d7080c1 Built: Wed Feb 20 02:26:20 2019 OS/Arch: linux/amd64 Experimental: false
次にdockerのランタイムをnvidiaのものへ変更をしていきます.
下記のコマンドを実行してnvidiaランタイムをダウンロード後,/etc/docker/daemon.jsonをnvidiaランタイムに変更します.
$ sudo apt-get install nvidia-container-runtime $ sudo tee /etc/docker/daemon.json <<EOF { "default-runtime": "nvidia", "runtimes": { "nvidia": { "path": "/usr/bin/nvidia-container-runtime", "runtimeArgs": [] } } } EOF $ sudo pkill -SIGHUP dockerd $ sudo systemctl daemon-reload $ sudo systemctl restart docker
最後にnvidiaのランタイムが正しく読み込まれているかの確認を行います. 下記コマンドを実行後,GPUの情報が表示されれば正しく読み込まれています.
$ sudo docker run --rm nvidia/cuda nvidia-smi Sun Jun 7 20:08:01 2020 +-----------------------------------------------------------------------------+ | NVIDIA-SMI 440.44 Driver Version: 440.44 CUDA Version: 10.2 | |-------------------------------+----------------------+----------------------+ | GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. | |===============================+======================+======================| | 0 Quadro GP100 Off | 00000000:18:00.0 Off | Off | | 45% 62C P0 96W / 235W | 959MiB / 16276MiB | 100% Default | +-------------------------------+----------------------+----------------------+ | 1 Quadro GP100 Off | 00000000:3B:00.0 Off | Off | | 46% 64C P0 163W / 235W | 643MiB / 16278MiB | 100% Default | +-------------------------------+----------------------+----------------------+ | 2 Quadro GP100 Off | 00000000:5E:00.0 Off | Off | | 49% 68C P0 197W / 235W | 643MiB / 16278MiB | 100% Default | +-------------------------------+----------------------+----------------------+ | 3 Quadro GP100 Off | 00000000:86:00.0 Off | Off | | 48% 66C P0 171W / 235W | 643MiB / 16278MiB | 96% Default | +-------------------------------+----------------------+----------------------+ +-----------------------------------------------------------------------------+ | Processes: GPU Memory | | GPU PID Type Process name Usage | |=============================================================================| +-----------------------------------------------------------------------------+
Kubernetes(以前と同様#1)
最後にKubernetesをインストールしていきます.何もツールを用いずインストールするを行うのは非常に難易度が高いため,今回はKubernetes公式もオススメしているkubeadmを用いてインストールを行っていきます.
以下のコマンドを実行してGoogleリポジトリの登録を行い,その後パッケージのインストールを行います.
$ sudo apt-get update $ sudo apt-get update && sudo apt-get install -y apt-transport-https $ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - $ echo deb http://apt.kubernetes.io/ kubernetes-xenial main | sudo tee -a /etc/apt/sources.list.d/kubernetes.list > /dev/null $ sudo apt-get update $ sudo apt-get install -y kubectl kubelet kubeadm
Step03:1台目のcontrol plane nodeのセットアップ
はじめにで述べたようにkube-vipを用いてHA構成の設定をした後, Step01でスワップ機能をオフにしましたが,念のためにもう一度オフにしておきます. その後,kubeadmを用いてセットアップしていきます. また,一台目のcontrol plane nodeとそれ以外のcontrol plane nodeでは設定が少し違う事に注意してください.
kube-vip
まずはじめにkube-vipについて軽く紹介した後に,実際の構築を行なっていきたいと思います.
kube-vipとは
kube-vipとは,以下アーキテクチャ図のようにK8s clusterのcontrol plane node内にデプロイされる仮想IPの管理とLoad Balancingの機能を兼ね備えた静的Podの事です.旧来のHA構成においてKeepAlivedが行なっていた仮想IPの管理と,HAProxyが行なっていたLoadBalancingを一つにまとめたようなコンポーネントになっております.
kube-vipの設定
まず以下のようなマニフェストを「config.yaml」を「/etc/kube-vip/」に作成してください. 注意点として以下の設定はcontrol plane node3台を想定したマニフェストとなっているためそれ以外の構成の場合は適宜変更してください.(control plane nodeは3台以上用意しないと障害耐性がない.)
remotePeers: - id: ${ID_NOT_LOCAL_NODE} address: ${IP_ADDR_NOT_LOCAL_NODE} port: 10000 - id: ${ID_NOT_LOCAL_NODE} address: ${IP_ADDR_NOT_LOCAL_NODE} port: 10000 localPeer: ${ID_LOCAL_NODE} address: ${IP_ADDR_LOCAL_NODE} port: 10000 vip: ${VI_IP_ADDR} gratuitousARP: true singleNode: false startAsLeader: true interface: ${NETWORK_INTERFACE_NAME} loadBalancers: - name: Kubernetes Control Plane type: tcp port: 6443 bindToVip: true backends: - port: 6444 address: ${IP_ADDR_CONTROL_PLANE_NODE} - port: 6444 address: ${IP_ADDR_CONTROL_PLANE_NODE} - port: 6444 address: ${IP_ADDR_CONTROL_PLANE_NODE}
また,以下マニフェスト内で「${hogehoge}」になっている部分は以下の表を参考に使用する環境に合わせて設定してください.
変数名 | 説明 |
---|---|
ID_NOT_LOCAL_NODE | control plane nodeのうち1台目以外のID*1 |
ID_NOT_LOCAL_NODE | control plane nodeのうち1台目のID*1 |
IP_ADDR_NOT_LOCAL_NODE | control plane nodeのうち1台目以外のIPアドレス |
IP_ADDR_LOCAL_NODE | control plane nodeのうち1台目以外のIPアドレス |
VI_IP_ADDR | kube-apiサーバのエンドポイントとして使用する仮想IPアドレス*2 |
NETWORK_INTERFACE_NAME | 使用するネットワークインターフェース名(例:eno0) |
P_ADDR_CONTROL_PLANE_NODE | control plane nodeのIPアドレス |
1 一意であればなんでもOK 2 必ず使用していないIPアドレスを使用しなければならない.
マニフェストが完成したら以下のdocker コマンドを実行して設定を反映させます.
$ docker run -it --rm plndr/kube-vip:0.1.1 /kube-vip sample manifest \ | sed "s|plndr/kube-vip:'|plndr/kube-vip:0.1.1'|" \ | sudo tee /etc/kubernetes/manifests/kube-vip.yaml
以下のような出力がえられると成功です.
apiVersion: v1 kind: Pod metadata: creationTimestamp: null name: kube-vip namespace: kube-system spec: containers: - command: - /kube-vip - start - -c - /vip.yaml image: 'plndr/kube-vip:0.1.1' name: kube-vip resources: {} securityContext: capabilities: add: - NET_ADMIN - SYS_TIME volumeMounts: - mountPath: /vip.yaml name: config hostNetwork: true volumes: - hostPath: path: /etc/kube-vip/config.yaml name: config status: {}
kubeadmでのセットアップ
「sudo kubeadm init」コマンドのオプション「----pod-network-cidr」は,クラスタ内ネットワークの構築にFlannelを使用する場合の設定です. 他の方法で構築する場合は以下のKubernetes公式ページを参考に設定してください.
また,「--control-plane-endpoint」はkube-vipの${VI_IP_ADDR}を入力する事でkube-apiサーバのエンドポイントを設定し,「--upload-certs」はcontrol plane node間で自動的に証明書の交換を行なってくれるオプション(以前はこのオプションがなく手動で証明書交換を行なっていた.)で,「--apiserver-bind-port」はkube-apiサーバ用ポートです. ここで,kubeadmのドキュメント kubeadm init | Kubernetes にあるように,アップロードされたcontrol plane nodeの証明書は有効期限が2時間しかありませんので注意してください.
$ sudo kubeadm init --control-plane-endpoint ${VI_IP_ADDR}:6443 --pod-network-cidr=10.244.0.0/16 --upload-certs --apiserver-bind-port 6444 W0608 17:35:35.559622 19592 version.go:102] could not fetch a Kubernetes version from the internet: unable to get URL "https://dl.k8s.io/release/stable-1.txt": Get https://storage.googleapis.com/kubernetes-release/release/stable-1.txt: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers) W0608 17:35:35.559725 19592 version.go:103] falling back to the local client version: v1.18.3 W0608 17:35:35.559949 19592 configset.go:202] WARNING: kubeadm cannot validate component configs for API groups [kubelet.config.k8s.io kubeproxy.config.k8s.io] [init] Using Kubernetes version: v1.18.3 [preflight] Running pre-flight checks [WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/ [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' [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 [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 [k8s-m0 kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx] [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 [k8s-m0 localhost] and IPs [xxx.xxx.xxx.xxx 127.0.0.1 ::1] [certs] Generating "etcd/peer" certificate and key [certs] etcd/peer serving cert is signed for DNS names [k8s-m0 localhost] and IPs [xxx.xxx.xxx.xxx 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" [endpoint] WARNING: port specified in controlPlaneEndpoint overrides bindPort in the controlplane address [kubeconfig] Writing "admin.conf" kubeconfig file [endpoint] WARNING: port specified in controlPlaneEndpoint overrides bindPort in the controlplane address [kubeconfig] Writing "kubelet.conf" kubeconfig file [endpoint] WARNING: port specified in controlPlaneEndpoint overrides bindPort in the controlplane address [kubeconfig] Writing "controller-manager.conf" kubeconfig file [endpoint] WARNING: port specified in controlPlaneEndpoint overrides bindPort in the controlplane address [kubeconfig] Writing "scheduler.conf" kubeconfig file [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" W0608 17:35:37.730317 19592 manifests.go:225] the default kube-apiserver authorization-mode is "Node,RBAC"; using "Node,RBAC" [control-plane] Creating static Pod manifest for "kube-scheduler" W0608 17:35:37.730883 19592 manifests.go:225] the default kube-apiserver authorization-mode is "Node,RBAC"; using "Node,RBAC" [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 21.578061 seconds [upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace [kubelet] Creating a ConfigMap "kubelet-config-1.18" in namespace kube-system with the configuration for the kubelets in the cluster [upload-certs] Storing the certificates in Secret "kubeadm-certs" in the "kube-system" Namespace [upload-certs] Using certificate key: ffa4187d479b4543ef2543e458cc5aa791c37b9159defa35e4d463518a871e15 [mark-control-plane] Marking the node k8s-m0 as control-plane by adding the label "node-role.kubernetes.io/master=''" [mark-control-plane] Marking the node k8s-m0 as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule] [bootstrap-token] Using token: iri98c.vvgys2r619z676d5 [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 [endpoint] WARNING: port specified in controlPlaneEndpoint overrides bindPort in the controlplane address [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 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/ You can now join any number of the control-plane node running the following command on each as root: kubeadm join xxx.xxx.xxx.xxx:6443 --token hogehoge \ --discovery-token-ca-cert-hash sha256:hogehoge \ --control-plane --certificate-key hogehoge Please note that the certificate-key gives access to cluster sensitive data, keep it secret! As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use "kubeadm init phase upload-certs --upload-certs" to reload certs afterward. Then you can join any number of worker nodes by running the following on each as root: kubeadm join xxx.xxx.xxx.xxx:6443 --token hogehoge \ --discovery-token-ca-cert-hash sha256:hogehoge
下の方に「Your Kubernetes control-plane has initialized successfully!」と表示されれば成功です. control plane nodeを冗長化する際に「kubeadm join xxx.xxx.xxx.xxx:6443 --token hogehoge \ --discovery-token-ca-cert-hash sha256:hogehoge \ --control-plane --certificate-key hogehoge」を,nodeを追加する際に「kubeadm join xxx.xxx.xxx.xxx:6443 --token hogehoge \ --discovery-token-ca-cert-hash sha256:hogehoge」を使用するのでどこかに保存しておいてください.このtokenの有効期限は24時間のため有効期限が切れた場合は以下のコマンドで生成済みのtokenの有無を確認の上新規発行を行ってください.
$ sudo kubeadm token list [sudo] michiru のパスワード: TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS hogehoge 24h 2020-06-09T17:36:00+09:00 authentication,signing The default bootstrap token generated by 'kubeadm init'. system:bootstrappers:kubeadm:default-node-token $ sudo kubeadm token create --print-join-command kubeadm join xxx.xxx.xxx.xxx:6443 --token hogehogehoge --discovery-token-ca-cert-hash sha256:hogehogehoge
ここで設定ミスをして,もう一度「sudo kubeadm init」コマンドを実行する場合「sudo kubeadm reset」を実行してkubeadmの設定をリセットしてから再度「sudo kubeadm init」コマンドを実行してください.
次に,kubectlで使用する認証ファイルの準備を行うため、以下のコマンドを実行してください.
$ mkdir -p $HOME/.kube $ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config $ sudo chown $(id -u):$(id -g) $HOME/.kube/config
CNIの設定
以下のコマンドでflannelを用いて構築していきます.
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml podsecuritypolicy.policy/psp.flannel.unprivileged created clusterrole.rbac.authorization.k8s.io/flannel created 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
念の為に正しく設定できているか確認しておきます.
$ kubectl get po -n kube-system NAME READY STATUS RESTARTS AGE coredns-66bff467f8-lnp56 1/1 Running 2 9h etcd-k8s-m0 1/1 Running 0 9h kube-apiserver-k8s-m0 1/1 Running 0 9h kube-controller-manager-k8s-m0 1/1 Running 1 9h kube-flannel-ds-amd64-bddvt 1/1 Running 1 9h kube-proxy-7pqtt 1/1 Running 0 9h kube-scheduler-k8s-m0 1/1 Running 1 9h kube-vip-k8s-m0 1/1 Running 0 9h
ここで,すべてのサービスのSTATUSがRunningになっていることを確認してください. 特にcorednsが正しく起動できていない場合,Step01の設定が正しく行えていないことが考えられるためよく確認してください
Step04:2台目以降のcontrol plane nodeのセットアップ
2台目以降control plane nodeの何台追加しても同じ設定になります. kube-vipとkubeadmの実行順序だけ気をつけてください.
kubeadmでのセットアップ
一台目のcontrol plane node構築時に出力されたコマンドを実行するだけです.
$ kubeadm join xxx.xxx.xxx.xxx:6443 --token hogehoge \ --discovery-token-ca-cert-hash sha256:hogehoge \ --control-plane --certificate-key hogehoge [preflight] Running pre-flight checks [preflight] Reading configuration from the cluster... [preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml' [preflight] Running pre-flight checks before initializing the new control plane instance [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' [download-certs] Downloading the certificates in Secret "kubeadm-certs" in the "kube-system" Namespace [certs] Using certificateDir folder "/etc/kubernetes/pki" [certs] Generating "etcd/peer" certificate and key [certs] etcd/peer serving cert is signed for DNS names [k8s-m1 localhost] and IPs [xxx.xxx.xxx.xxx 127.0.0.1 ::1] [certs] Generating "apiserver-etcd-client" certificate and key [certs] Generating "etcd/server" certificate and key [certs] etcd/server serving cert is signed for DNS names [k8s-m1 localhost] and IPs [xxx.xxx.xxx.xxx 127.0.0.1 ::1] [certs] Generating "etcd/healthcheck-client" certificate and key [certs] Generating "apiserver" certificate and key [certs] apiserver serving cert is signed for DNS names [k8s-m1 kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx] [certs] Generating "apiserver-kubelet-client" certificate and key [certs] Generating "front-proxy-client" certificate and key [certs] Valid certificates and keys now exist in "/etc/kubernetes/pki" [certs] Using the existing "sa" key [kubeconfig] Generating kubeconfig files [kubeconfig] Using kubeconfig folder "/etc/kubernetes" [kubeconfig] Writing "admin.conf" kubeconfig file [kubeconfig] Writing "controller-manager.conf" kubeconfig file [kubeconfig] Writing "scheduler.conf" kubeconfig file [control-plane] Using manifest folder "/etc/kubernetes/manifests" [control-plane] Creating static Pod manifest for "kube-apiserver" W0608 17:41:14.871995 10536 manifests.go:225] the default kube-apiserver authorization-mode is "Node,RBAC"; using "Node,RBAC" [control-plane] Creating static Pod manifest for "kube-controller-manager" W0608 17:41:14.876431 10536 manifests.go:225] the default kube-apiserver authorization-mode is "Node,RBAC"; using "Node,RBAC" [control-plane] Creating static Pod manifest for "kube-scheduler" W0608 17:41:14.877076 10536 manifests.go:225] the default kube-apiserver authorization-mode is "Node,RBAC"; using "Node,RBAC" [check-etcd] Checking that the etcd cluster is healthy [kubelet-start] Downloading configuration for the kubelet from the "kubelet-config-1.18" ConfigMap in the kube-system namespace [kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml" [kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env" [kubelet-start] Starting the kubelet [kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap... [etcd] Announced new etcd member joining to the existing etcd cluster [etcd] Creating static Pod manifest for "etcd" [etcd] Waiting for the new etcd member to join the cluster. This can take up to 40s {"level":"warn","ts":"2020-06-08T17:41:31.138+0900","caller":"clientv3/retry_interceptor.go:61","msg":"retrying of unary invoker failed","target":"passthrough:///https://xxx.xxx.xxx.xxx:2379","attempt":0,"error":"rpc error: code = DeadlineExceeded desc = context deadline exceeded"} [upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace [mark-control-plane] Marking the node k8s-m1 as control-plane by adding the label "node-role.kubernetes.io/master=''" [mark-control-plane] Marking the node k8s-m1 as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule] This node has joined the cluster and a new control plane instance was created: * Certificate signing request was sent to apiserver and approval was received. * The Kubelet was informed of the new secure connection details. * Control plane (master) label and taint were applied to the new node. * The Kubernetes control plane instances scaled up. * A new etcd member was added to the local/stacked etcd cluster. To start administering your cluster from this node, 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 Run 'kubectl get nodes' to see this node join the cluster.
続いて以下のコマンドを実行し,kube-configファイルを作成します.
$ mkdir -p $HOME/.kube &&\ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config &&\ sudo chown $(id -u):$(id -g) $HOME/.kube/config
kube-vip
ドキュメント Load Balancing a Kubernetes Cluster (Control-Plane) にあるように,2台目以降のcontrol plane nodeをセットアップする際はK8s clusterに参加させるより前にkube-vip静的Pod用マニフェストを準備しては行けません.
2台目以降でも使用するkube-vip静的Pod用yamlファイルは1台目とほとんど変わりませんが,少し変更箇所もあるので以下のように「/etc/kube-vip/config.yaml」を作成してください.
remotePeers: - id: ${ID_NOT_LOCAL_NODE} address: ${IP_ADDR_NOT_LOCAL_NODE} port: 10000 - id: ${ID_NOT_LOCAL_NODE} address: ${IP_ADDR_NOT_LOCAL_NODE} port: 10000 localPeer: ${ID_LOCAL_NODE} address: ${IP_ADDR_LOCAL_NODE} port: 10000 vip: ${VI_IP_ADDR} gratuitousARP: true singleNode: false startAsLeader: false interface: ${NETWORK_INTERFACE_NAME} loadBalancers: - name: Kubernetes Control Plane type: tcp port: 6443 bindToVip: true backends: - port: 6444 address: ${IP_ADDR_CONTROL_PLANE_NODE} - port: 6444 address: ${IP_ADDR_CONTROL_PLANE_NODE} - port: 6444 address: ${IP_ADDR_CONTROL_PLANE_NODE}
1台目のyamlファイルとの変更点は以下の通りです. また,「LOACL_NODE」がついているものはインストールするホストにしたがって変更してください.
...(省略) vip: ${VI_IP_ADDR} gratuitousARP: true singleNode: false - startAsLeader: true + startAsLeader: false interface: ${NETWORK_INTERFACE_NAME} ...(省略)
Step05:nodeのセットアップ
nodeの方も,念のためにもう一度スワップ機能をオフにしておきます. その後一台目のcotrol planeで発行されて保存したtokenを入力します. またnodeは何台目であっても設定方法が同じです.
$ kubeadm join xxx.xxx.xxx.xxx:6443 --token hogehoge \ > --discovery-token-ca-cert-hash sha256:hogehoge W0608 17:52:22.187551 3076 join.go:346] [preflight] WARNING: JoinControlPane.controlPlane settings will be ignored when control-plane flag is not set. [preflight] Running pre-flight checks [preflight] Reading configuration from the cluster... [preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml' [kubelet-start] Downloading configuration for the kubelet from the "kubelet-config-1.18" ConfigMap in the kube-system namespace [kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml" [kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env" [kubelet-start] Starting the kubelet [kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap... This node has joined the cluster: * Certificate signing request was sent to apiserver and a response was received. * The Kubelet was informed of the new secure connection details. Run 'kubectl get nodes' on the control-plane to see this node join the cluster.
「Run 'kubectl get nodes' on the control-plane to see this node join the cluster.」と最後に表示されればさきほど作成したK8s Clusterへの追加が成功しています.
Step06:最終確認
最後に,追加したnodeがK8s Clusterに追加されたか確認を行うためcontrol plane nodeもしかはkube-configを保持するローカルマシンで以下のコマンドを実行してください.
$ kubectl get nodes NAME STATUS ROLES AGE VERSION eriri Ready <none> 8h v1.18.3 k8s-m0 Ready master 8h v1.18.3 k8s-m1 Ready master 8h v1.18.3 michiru Ready master 8h v1.18.3 utaha Ready <none> 8h v1.18.3
K8s Cluster上でのDeepLearning環境の構築
この章では,先ほど構築したK8s Cluster上に機械学習環境の構築を行っていきます.
NVIDIA-device-plugin-for-Kubernetesの導入
NVIDIA-device-plugin-for-Kubernetesのdaemonsetをデプロイします. 今回は記事執筆時点で最新のbeta6を使用します.
$ kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/1.0.0-beta6/nvidia-device-plugin.yml daemonset.apps/nvidia-device-plugin-daemonset created
次にNVIDIA-device-plugin-for-Kubernetesデーモンセットが正常にデプロイできているかどうか確認します.「nvidia-device-plugin-daemonset-xxxxx」のSTATUSがRUNNINGになっていれば正常にデプロイできています.
セットアップが完了したので,テスト用Podをデプロイして確認します.デプロイするPodの中身は自分でマニフェストファイルを書くか,以下の内容で「sample.yaml」ファイルを作成してください.
piVersion: v1 kind: Pod metadata: name: gpu-pod spec: containers: - name: cuda-container image: nvidia/cuda:10.0-devel tty: true resources: limits: nvidia.com/gpu: 1 # requesting 1 GPUs
マニフェストが完成したら以下のコマンドを実行してgpu-podをデプロイし,デプロイしたPodのStatusがRunnningになっているかどうか確認します.
$ kubectl apply -f sample.yaml pod/gpu-pod created $ kubectl get pods --all-namespaces -o wide NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES default gpu-pod 1/1 Running 0 10s 10.244.1.5 Utaha <none> <none> (省略)
以上で全て完了です.
確認
全ての工程を正しく行えた場合以下のようなPodがkube-system namespaceにデプロイされているはずです.
$ kubectl get po -n kube-system NAME READY STATUS RESTARTS AGE coredns-66bff467f8-lnp56 1/1 Running 2 9h coredns-66bff467f8-tpcdf 1/1 Running 0 9h etcd-k8s-m0 1/1 Running 0 9h etcd-k8s-m1 1/1 Running 0 9h etcd-michiru 1/1 Running 0 9h kube-apiserver-k8s-m0 1/1 Running 0 9h kube-apiserver-k8s-m1 1/1 Running 0 9h kube-apiserver-michiru 1/1 Running 0 9h kube-controller-manager-k8s-m0 1/1 Running 1 9h kube-controller-manager-k8s-m1 1/1 Running 0 9h kube-controller-manager-michiru 1/1 Running 0 9h kube-flannel-ds-amd64-bddvt 1/1 Running 1 9h kube-flannel-ds-amd64-cbgx8 1/1 Running 2 9h kube-flannel-ds-amd64-h6bcv 1/1 Running 2 9h kube-flannel-ds-amd64-pdjxh 1/1 Running 0 9h kube-flannel-ds-amd64-xlbjj 1/1 Running 3 9h kube-proxy-7pqtt 1/1 Running 0 9h kube-proxy-b66mj 1/1 Running 0 9h kube-proxy-ht7dd 1/1 Running 0 9h kube-proxy-klgk4 1/1 Running 0 9h kube-proxy-tdzkb 1/1 Running 0 9h kube-scheduler-k8s-m0 1/1 Running 1 9h kube-scheduler-k8s-m1 1/1 Running 0 9h kube-scheduler-michiru 1/1 Running 0 9h kube-vip-k8s-m0 1/1 Running 0 9h kube-vip-k8s-m1 1/1 Running 0 9h kube-vip-michiru 1/1 Running 0 9h nvidia-device-plugin-daemonset-245zk 1/1 Running 0 7h16m nvidia-device-plugin-daemonset-6dcxx 1/1 Running 0 7h16m nvidia-device-plugin-daemonset-hgkwt 1/1 Running 0 7h16m nvidia-device-plugin-daemonset-sf7nl 1/1 Running 0 7h16m nvidia-device-plugin-daemonset-twnsb 1/1 Running 0 7h16m
出力結果を見てみるとetcd,kube-apiserver,kube-controller,kube-schedulerがしっかり冗長構成になっていることがわかります.この記事ではやりませんが,実際にどれか一つのマシンを落としてもきちんとK8s clusterは稼働し続けていることが確認できると思います.
おわりに
今回はkube-vipを用いてcontrol planeの冗長化を行いました. ドキュメント Creating a cluster with kubeadm | Kubernetes
By default, your cluster will not schedule Pods on the control-plane node for security reasons.
にあるように プロダクションで使用することを考えるとセキュリティ上の問題からcontrol planeとnodeは分離するべきですが台数を用意することができなかったため共存させる形になりました.誰かの参考になれば幸いです.