この記事のまとめ:
- Raspberry Pi 3B+を使ってKubernetesクラスターをAnsibleで構築する方法をまとめています。
背景
ずっとKubernetesの勉強をしようと思っていて、せっかくだからイチから作りたいよねってことでRaspberry PiでKubernetesクラスターを構築したいと思います。
何番煎じかわかりませんが、、、なんていうとネガティブに聞こえますが、先人が沢山いることは大変ありがたいです。
はじめに
今回行うKubernetesクラスターの構築はすべてAnsibleプレイブックとして私のGitHubリポジトリーに置いておきました。
hassiweb/k8s-install-on-rpi
Kubernetes install scripts on Raspberry Pi using Ansible - hassiweb/k8s-install-on-rpi
これ以降、Ansibleタスクのコードを記載しておりますが、GitHubリポジトリーが最新版ですので多少差分があるかもしれませんのがご了承ください。
なお、Ansibleを使わないでKubernetesをインストールする方法は本記事ではまとめておりません。実行コマンド等は基本的に下記の記事を参考にさせていただきましたので、Ansibleを使わずにインストールする場合にはこちらをご参考にされたほうがよいかと思います。
材料調達
下記がおおよそ必須材料です。
沢山税金を納めている方に朗報!埼玉県狭山市のふるさと納税で2万円の寄付でRaspberry Pi 3B+を1台返礼してくれます。他に寄付する予定がなければこれで手に入れるのもいいんじゃないでしょうか。
その他、他の方は小型無線LAN APを包含させてしまってどこでも無線でつながるようにされていたりしますが、今回はシンプルに有線ハブで接続するだけです。
下記はなんとなく基板を垂直に設置したいと思い立って購入したものです。
アクリル板の加工は下記の通りにしてもらっています。
- カットサイズ 縦:120mm 横:150mm 板厚:3.0mm (横は140mmでも十分そう)
- 切断処理:カンナ仕上げ
- 角丸め加工 ABCD端 半径:8mm
- 穴あけ加工 ABCD端 穴直径:3mm 距離X:8mm 距離Y:8mm
なお、天板には基板を止めるための穴あけ加工と積層式ケースの削りを自前で行っています。
裏から見たらこんな感じ。
下段にはスイッチングハブとUSB充電器が入っています。
上から見たらこんな感じ。
25mm間隔で並べてると5枚分くらい入りそう。
下準備
OSのインストールとSSHのセットアップ
OSにはRasbian (2019-04-08-raspbian-stretch-lite)を使用します。
なお、執筆時点での最新版のRaspbian BusterはDebian 10ベースなのですが、Dockerが最新版に対応していないため、Debian 9ベースのRaspbian Stretchを使います。
OSのセットアップとSSHでログインできるまで準備しておきます。
下記でそれらのセットアップ方法を紹介していますので必要であればご覧ください。
Raspberry Piの始め方
Raspberry Piを初めて買ってみたので購入先や初期設定について説明しています。
Ansible関連ファイル構成
クラスターを構築するとなると必然的に数が増えてきますので、ここからの作業はすべてAnsibleで設定を行います。
全ファイル構成は下記の通りです。
$ tree
.
├── ansible
│ ├── ansible.cfg
│ ├── inventory
│ │ └── inventory.ini
│ ├── k8s_provisioning.yml
│ ├── raspbian_provisioning.yml
│ └── roles
│ ├── common
│ │ ├── geerlingguy.docker_arm
│ │ │ │
│ │ │ :
│ │ │ └── xxx
│ │ ├── geerlingguy.ntp
│ │ │ │
│ │ │ :
│ │ │ └── xxx
│ │ └── ypsman.hostname
│ │ │
│ │ :
│ │ └── xxx
│ └── k8s
│ ├── provisioning
│ │ ├── defaults
│ │ │ └── main.yml
│ │ └── tasks
│ │ ├── common_setup.yml
│ │ ├── install_k8s_packages.yml
│ │ ├── main.yml
│ │ ├── provision_master.yml
│ │ ├── provision_workers.yml
│ │ └── store_info_to_join.yml
│ └── roles
│ └── tasks
│ └── main.yml
├── docker-compose.yml
├── Dockerfile
├── k8s_provisioning.sh
└── raspbian_provisioning.sh
以降でそれぞれのファイルの概要を説明していきます。
基本設定
Raspbianに下記の設定をしていきます。
- Dockerのインストール
- NTPの設定(必須じゃないです)
- ホスト名の変更(趣味です)
Ansibleの実行コンテナの作成
Ansibleは個人的にバージョン依存性が多々あると感じており、安定動作のためにAnsibleを実行するコンテナを作成してます。執筆時点の最新版であるv2.7.16を使用します。
Dockerfile
は下記の通りです。
FROM python:2.7
RUN echo "deb http://ppa.launchpad.net/ansible/ansible/ubuntu trusty main" >> /etc/apt/sources.list && \
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 93C4A3FD7BB9C367 && \
apt-get update && \
apt-get install -y ansible=2.7.16
WORKDIR /ansible
CMD bash
実行していることはAnsibleの公式のインストール方法のままです。
ビルドしてイメージを作っておきます。
$ docker build -t hassiweb/ansible:2.7.16 .
タグ名は適宜変更してください。
ansible.cfgの設定
コンテナの中からAnsibleを実行する場合には、ターゲットノードに必ず初めてSSHで接続することになるため、OpenSSHのチェック機能によってAnsibleを実行すると次のようなエラーが出ます。
TASK [Gathering Facts] ****************************************************************************************************************************************************fatal: [192.168.0.43]: FAILED! => {"msg": "Using a SSH password instead of a key is not possible because Host Key checking is enabled and sshpass does not support this. Please add this host's fingerprint to your known_hosts file to manage this host."}
これを回避するために ansible/ansible.cfg
に下記のように ssh_args
の設定を追加します。
[defaults]
forks = 4
log_path = ./ansible.log
host_key_cheking = False
gathering = smart
gather_subset = all
transport = smart
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
なお、下記の記事を参考にさせていただきました。
AnsibleのSSH接続エラーの回避設定 - Qiita
AnsibleのSSH接続エラーの回避設定、概要:Managed NodeにSSHで一度も接続をしたことが無い時や、fingerprintが変わった時、OpenSSHのチェック機能により警告が表示され、ansibleの実行…
Ansibleタスク
Ansible Galaxyから geerlingguy.docker_arm、geerlingguy.ntp、ypsman.hostname をインストールしてきます。
- geerlingguy.docker_armの修正点
デフォルトではdocker-composeをインストールしようとしますがこれをfalseにします。具体的には
ansible/roles/common/geerlingguy.docker_arm/defaults/main.yml
を下記のように編集します。
# Whether to install Docker Compose via Pip.
docker_install_compose: false
このタスクではPipでdocker-composeのインストールをしようとしますが、Pipをインストールしていないためです。なお、PipをインストールしてもPyPIで管理されているdocker-composeはPython 2.7ではarmプロセッサ対応していないようです。
タイムゾーンやNTPエリアを修正します。ansible/roles/common/geerlingguy.ntp/defaults/main.yml
を下記のように編集します。
---
ntp_enabled: true
ntp_timezone: Asia/Tokyo
ntp_manage_config: True
# NTP server area configuration (leave empty for 'Worldwide').
# See: http://support.ntp.org/bin/view/Servers/NTPPoolServers
ntp_area: 'asia'
ntp_servers:
- "0{{ '.' + ntp_area if ntp_area else '' }}.pool.ntp.org iburst"
- "1{{ '.' + ntp_area if ntp_area else '' }}.pool.ntp.org iburst"
- "2{{ '.' + ntp_area if ntp_area else '' }}.pool.ntp.org iburst"
- "3{{ '.' + ntp_area if ntp_area else '' }}.pool.ntp.org iburst"
ntp_restrict:
- "127.0.0.1"
- "::1"
このタスクでは変更したいホスト名はインベントリーに記載します。インベントリーファイル ansible/inventory/inventory.ini
に次のように host_name
の後に変更したいホスト名を記載します(この例ではrpi00
)。
[all:children]
master
workers
[master]
192.168.0.40 ansible_user=pi ansible_password=raspberry host_name=rpi00
[workers]
192.168.0.41 ansible_user=pi ansible_password=raspberry host_name=rpi01
192.168.0.42 ansible_user=pi ansible_password=raspberry host_name=rpi02
192.168.0.43 ansible_user=pi ansible_password=raspberry host_name=rpi03
Ansible Playbookの実行
先ほどインストールしたタスクを実行するplaybookを次のように書きます(ファイル名:ansible/raspbian_provisioning.yml
)。
---
- name: Provisioning Raspberri Pi
hosts: raspberry_pi
become: true
roles:
- name: common/geerlingguy.ntp
tags: ntp
- name: common/geerlingguy.docker_arm
tags: docker
- name: common/ypsman.hostname
tags: hostname
Ansible Playbookの実行はコンテナから行います。そのコマンドは下記の通りです。
$ docker run -it --rm -v $PWD/ansible:/ansible hassiweb/ansible:2.7.16 ansible-playbook raspbian_provisioning.yml -i inventory/inventory.ini
さて、ようやく下準備が完了しました。
Kubernetesのインストール
kubeadmを使ったKubernetesクラスターの構築を行いますが、引き続きAnsibleでタスクを記述していきます。Ansibleプレイブックとしての実行は最後に記載しますが、Kubernetesの構築手順に沿ってどのような処理をするのかとそのAnsibleタスクの記述を説明していきます。
なお、最新版のKubernetes v1.15.1でも基本的にはクラスターに属するすべてのラズパイで、swapをオフにすること、cgroup memoryをenableにすることを忘れなければそこまでハマらなさそうです。
#むしろ、Ansibleのモジュールとかの使い方を知らなさ過ぎてハマりました…。
クラスター構成
今回は4台のラズパイがありますので、1台をmaster、残り3台をworkerとしてセットアップします。
共通設定
Kubernetesパッケージのインストール
まずはクラスターを構成するすべてのノードでkubectl、kubelet、kubeadmのインストールします(ファイル名:ansible/roles/k8s/provisioning/tasks/install_k8s_packages.yml
)。
---
- name: Ensure dependencies are installed.
apt:
name:
- apt-transport-https=1.4.9
- ca-certificates=20161130+nmu1+deb9u1
state: present
- name: Add Kubernetes apt key.
apt_key:
url: https://packages.cloud.google.com/apt/doc/apt-key.gpg
state: present
- name: Add Kubernetes repository.
apt_repository:
repo: "deb https://apt.kubernetes.io/ kubernetes-xenial main"
state: present
update_cache: true
- name: Add Kubernetes apt preferences file to pin a version.
template:
src: apt-preferences-kubernetes.j2
dest: /etc/apt/preferences.d/kubernetes
- name: Update cache.
apt: update_cache=yes cache_valid_time=3600
- name: Install Kubernetes packages.
apt:
name:
- kubelet
- kubeadm
- kubectl
メモリー関連設定
ラズパイはメモリー管理が特殊な設定になっているようで、kubeadmを実行する前に、仮想メモリーであるswapの設定をオフにすることと、メモリー管理をcgroupで行うための設定を行います(ファイル名:ansible/roles/k8s/provisioning/tasks/common_setup.yml
)。
---
# Disable swap
- name: Disable swap.
command: "{{ item }}"
with_items:
- dphys-swapfile swapoff
- dphys-swapfile uninstall
- update-rc.d dphys-swapfile remove
# Enable cgroup memory
- name: Check if cgroup memory is enabled.
shell: "cat /proc/cgroups | grep memory | awk '{print $4}'"
register: cgroup_memory_status
- name: Enable cgroup memory if desabled.
lineinfile:
path: /boot/cmdline.txt
backrefs: true
state: present
regexp: '(.*)$'
line: '\1 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory'
when: cgroup_memory_status.stdout|int == 0
- name: Restart machine.
shell: shutdown -r now
async: 1
poll: 0
ignore_errors: true
when: cgroup_memory_status.stdout|int == 0
- name: Wait for reboot.
wait_for_connection:
delay: 30
timeout: 300
when: cgroup_memory_status.stdout|int == 0
Masterの設定
kubeadmを使ってmasterのセットアップをし、その後ネットワークプラグインとしてFlannelの設定をします(ファイル名:ansible/roles/k8s/provisioning/tasks/provision_master.yml
)。
----
- name: Initialize Kubernetes master with kubeadm init.
command: >
kubeadm init
--pod-network-cidr='10.244.0.0/16'
--kubernetes-version 'stable-1.15'
register: kubeadmin_init
- name: Print the init output to screen.
debug:
var: kubeadmin_init.stdout
- name: Ensure .kube directory exists.
file:
path: ~/.kube
state: directory
- name: Symlink the kubectl admin.conf to ~/.kube/conf.
file:
src: /etc/kubernetes/admin.conf
dest: ~/.kube/config
state: link
- name: Configure Flannel networking.
command: "{{ item }}"
with_items:
- kubectl apply -f 'https://raw.githubusercontent.com/coreos/flannel/62e44c867a2846fefb68bd5f178daf4da3095ccb/Documentation/kube-flannel.yml'
register: flannel_result
changed_when: "'created' in flannel_result.stdout"
ここまででmasterの設定は完了です。
Workerの設定
Workerの設定には、kubeadm init
コマンドでmasterを設定した際に、別のノードをworkerとしてクラスターにjoinするための情報が次のように出力されます。
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 192.168.0.40:6443 --token 9ypxep.s85dtgydsxq3yv0t \
--discovery-token-ca-cert-hash sha256:d4b3748eb02991613e885f247cc7dac9b27564345f16e75cf52eeb642db12f41
基本的にはこれらの情報を使うのですが、このコマンドを実行後に改めてこれらの情報を取得する方法をたまたま紹介してくれている方がおられたのでそのコードをそのまま拝借して、workerの設定を行います。
How do I set register a variable to persist between plays in ansible?
I have an ansible playbook, where I’d like a variable I register on one machine to be available on another.
In my case, I’d like to run a command on localhost, in this case git rev-parse --abbrev…
まずは、これらの情報を別のタスクから呼び出せるようにするために、hostvars
に格納します(ファイル名:ansible/roles/k8s/provisioning/tasks/store_info_to_join.yml
)。
---
- name: "Cluster token"
shell: kubeadm token list | cut -d ' ' -f1 | sed -n '2p'
register: K8S_TOKEN
- name: "CA Hash"
shell: openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'
register: K8S_MASTER_CA_HASH
- name: "Add K8S Token and Hash to dummy host"
add_host:
name: "K8S_TOKEN_HOLDER"
token: "{{ K8S_TOKEN.stdout }}"
hash: "{{ K8S_MASTER_CA_HASH.stdout }}"
- name:
debug:
msg: "\[Master] K8S_TOKEN_HOLDER K8S token is {{ hostvars['K8S_TOKEN_HOLDER'\]['token'] }}"
- name:
debug:
msg: "\[Master] K8S_TOKEN_HOLDER K8S Hash is {{ hostvars['K8S_TOKEN_HOLDER'\]['hash'] }}"
このタスクを利用して、workerをクラスターにjoinさせます(ファイル名:ansible/roles/k8s/provisioning/tasks/provision_workers.yml
)。
---
- name: Provision workers
shell: >
kubeadm join --token={{ hostvars\['K8S_TOKEN_HOLDER'\]['token'] }}
--discovery-token-ca-cert-hash sha256:{{ hostvars\['K8S_TOKEN_HOLDER'\]['hash'] }}
{{ item }}:{{ kubernetes_apiserver_port }}
with_items: "{{ groups['master'] }}"
when: "'workers' in group_names"
ここまでで一連の流れは終わりです。上記の初期設定はすべて ansible/roles/k8s/provisioning/tasks/main.yml
から呼び出されて実行されます。
---
# Common
- name: Common setup.
include: common_setup.yml
# Provision Master
- name: Check if Kubernetes has already been initialized.
stat:
path: /etc/kubernetes/admin.conf
register: kubernetes_init_stat
- name: Provision master if not installed yet
include: provision_master.yml
when: "'master' in group_names and not kubernetes_init_stat.stat.exists"
# Provision Workers
- name: Sotre k8s token and hash to dummy host
include: store_info_to_join.yml
when: "'master' in group_names"
- name: Provision workers
include: provision_workers.yml
when: "'workers' in group_names"
まだAnsibleプレイブックの実行は行っていませんが、ここまでの手順でKubernetesクラスターの構築は完了です。
ただし、workerをjoinさせただけだと表示上、workerのroleが設定されていないのでroleとして”worker”と表示させる設定を行います(ファイル名:ansible/roles/k8s/roles/tasks/main.yml
)。
---
- name: Change roles of workers
shell: >
kubectl label node {{ hostvars\[item\]['host_name'] }} node-role.kubernetes.io/worker=worker
with_items: "{{ groups['workers'] }}"
最後にこれまでのすべてのKubernetesクラスターの構築手順を行うプレイブックを書きます(ファイル名:ansible/k8s_provisioning.yml
)。
---
- name: Provisioning Kubernetes master
hosts: master
become: true
roles:
- name: k8s/provisioning
- name: Provisioning Kubernetes workers
hosts: workers
become: true
roles:
- name: k8s/provisioning
- name: Change roles of workers
hosts: master
become: true
roles:
- name: k8s/roles
上記のプレイブックをDockerコンテナーから実行します(ファイル名:k8s_provisioning.sh
)。
ディレクトリーについては適宜修正してください。
#!/bin/sh
docker run -it --rm -v $PWD/ansible/k8s_on_rpi/ansible:/ansible hassiweb/ansible:rpi ansible-playbook k8s_provisioning.yml -i inventory/inventory.ini
上記のAnsibleプレイブックの実行が無事完了していれば、masterにログインして下記を実行すると次のようにクラスターを構築するノードを表示できるはずです。
$ sudo kubectl get nodes
NAME STATUS ROLES AGE VERSION
rpi00 Ready master 2d1h v1.15.1
rpi01 Ready worker 45h v1.15.1
rpi02 Ready worker 45h v1.15.1
rpi03 Ready worker 45h v1.15.1
ここまで表示できれば完了です。お疲れさまでした!
今回は以上です。
最後まで読んでいただき、ありがとうございます。
関連記事
Raspberry PiのプロビジョニングをAnsibleで行う
Ansibleを使ってRaspberry Piのプロビジョニングのやり方を紹介しています。
コメント
コメントを投稿