🥮

Jetson nanoをk8sクラスタ参加させた(kubeadm)

2021/12/18に公開約12,100字2件のコメント

Jetson nanoをkubeadm製のkubernetesクラスタに追加するために、OSのセットアップ、カーネルのビルド・・・など、必要ないろいろを設定しました。

環境

  • Jetson nano 2GB Developer Kit
  • SDカード:32GB
    • 正直64GBあったほうが良い(makeが失敗する可能性あるため)

Jetson nanoのセットアップ

以下のプロジェクトで頒布されているイメージ(jetcard_v0p0p0.zip)を用います。

https://github.com/NVIDIA-AI-IOT/jetcard
こちらはJetson nanoをヘッドレスでインストールできるようにと進められているプロジェクトです。

jetcard_v0p0p0.zipをmicroSDに書き込み、Jetson nanoをLANケーブルで接続して電源をつければ、avahi-daemonにより、以下でアクセスが可能です。

ssh jetson@jetson.local

パスワードはjetsonになります。http://<IPアドレス>:8888でJupiter notebookにアクセスもできるようですが、今回はターミナルですべて完結させます。

jetcardスクリプトの削除

容量を占めている、不要な/home/jetson/jetcard配下を削除します。
メモリーカード容量が大きい場合は削除する必要はありません。

sudo rm -rf /home/jetson/jetcard/

ユーザ登録

鍵でアクセスできるユーザを作成します。ついでにNOPASSWDも設定しておきます。

sudo su -
USERNAME="admin"
useradd -m -U -u 1001 -s /bin/bash -G sudo ${USERNAME}
mkdir /home/${USERNAME}/.ssh
cat <<EOF > /home/${USERNAME}/.ssh/authorized_keys
(登録するPublicキーを記載)
EOF
chown ${USERNAME}. -R /home/${USERNAME}/
echo "${USERNAME} ALL=(ALL) NOPASSWD: ALL" | tee /etc/sudoers.d/099_${USERNAME}-nopasswd
chmod 440 /etc/sudoers.d/099_${USERNAME}-nopasswd
gpasswd -a ${USERNAME} video

CUIモードに切り替え

サーバ用途で使用するため、起動モードを切り替えておきます。

systemctl get-default 
systemctl set-default multi-user.target  
reboot

登録ユーザでsshできることを確認

作成したユーザでsshできること、ipアドレスが変わっていることを確認します。

ssh <ユーザー名>@jetson.local  
ip a

netplanでIPアドレスを固定

netplanが使いやすいので、netplanをインストールしてIPアドレスを固定します。

sudo su -
apt install -y netplan.io
cat <<EOF > /etc/netplan/99-manual.yaml
network:
    renderer: NetworkManager
    version: 2
    ethernets:
        eth0:
            addresses:
            - 192.168.3.25/24
            dhcp4: false
            dhcp6: false
            gateway4: 192.168.3.1
            nameservers:
                addresses:
                - 192.168.3.250
                - 8.8.8.8
                search:
                - neko.lab
EOF
netplan apply

apply後、ネットワークが切断されるので、再度接続し直します。

カーネルのリビルド

jetcardのイメージはk8sで使用する一部のカーネルパラメータが設定されていないみたいです。

カーネルの再ビルド前にkubeadm initをした際に発生したエラー
CONFIG_IP_NF_TARGET_REDIRECT: not set
CONFIG_AUFS_FS: not set - Required for aufs.
CONFIG_CGROUP_HUGETLB: not set - Required for hugetlb cgroup.
CGROUPS_HUGETLB: missing
error execution phase preflight: [preflight] Some fatal errors occurred:
	[ERROR SystemVerification]: unexpected kernel config: CONFIG_IP_NF_TARGET_REDIRECT

先駆者の方によると、カーネルをビルドするしか無い(曲解)ということです。

https://qiita.com/ysakashita/items/566e082a5d060eef5046
上記に倣ってカーネルをリビルドしていきます。
sudo su -
wget https://developer.nvidia.com/embedded/dlc/public_sources_Nano
tar xvf public_sources_Nano
cd public_sources
tar xf kernel_src.tbz2
cd kernel/kernel-4.9
zcat /proc/config.gz > .config

設定方法

makeのnconfig機能を使います。

# apt install -y libncurses5-dev #←curses.hが無いとエラーが出た場合
make nconfig

↑を入力すると次のような画面が出てきます。

Calicoで必要なカーネル機能がどこにあるかなどが明確に書かれた資料が見つからなかったため、ラズパイのカーネル設定も参考にしながら、この画面のNetworking support --> Networking options内の殆どの項目をM(module)またはY(buildin)に設定してみました。

本記事添付にしたかったのですが、diffをとってもかなりの量だったため、.configのソースをアップロードしておきました。どうぞ使ってください。

https://github.com/nkte8/labo/blob/2021-12-18-r01/jetson-kernel-config

上を使う場合は、以下のコマンドでダウンロードできます。

rm .config # zcatですでに.configを生成している場合は削除 
wget https://raw.githubusercontent.com/nkte8/labo/2021-12-18-r01/jetson-kernel-config -O .config

上記はk8s-1.21.4、CNIはCalico v3.20.1IPIPモードで動作を確認しています。(Calicoの設定等については、同リポジトリのansibleディレクトリ内を参照してください。)

カーネルのコンパイル

カーネルのコンパイルをしていきます。パラメータを結構設定したためか、6時間ぐらいかかりました。

make prepare && make modules_prepare && make -j5 Image && make -j5 modules
ls -h arch/arm64/boot/Image

容量は8GBにも膨れ上がります。swapoffをしたり、/var/swapfileを削除したりなどをしてギリギリ足りました。

root@jetson:~/public_sources/kernel/kernel-4.9# du -sh ./
8.6G	./

ゆくゆくはJetson上ではなく他環境でクロスコンパイルなどを検討したほうが良さそうです。

カーネルの置き換え

Jetsonは/boot/Imageをカーネルとして読み込むようなので、新しく作成したイメージを配置します。

# cp /boot/Image /boot/Image.org # ディスクに余裕があればバックアップしたほうが良い
cp arch/arm64/boot/Image /boot/Image

モジュールをインストールし、再起動します。

make modules_install
reboot

再度ログインしてunameすると、カーネルの日付がビルドした日付になっています。

uname -a
Linux jetson 4.9.140 #1 SMP PREEMPT Wed Dec 15 23:49:35 PST 2021 aarch64 aarch64 aarch64 GNU/Linux

以上でカーネルビルドは完了です。

カーネルビルド結果の削除

Jetson nanoの方はかなりディスク容量を消費するので、ディレクトリを削除しておきましょう。

sudo su -
rm -rf ~/public_sources/ 
rm -f ~/public_sources_Nano

OSのセットアップ

desktop環境を使う気が無いので、ubuntu-serverを入れて不要そうなパッケージについてはアンインストールしてしまいます。

ubuntu-serverの導入

ubuntu-desktopを削除しない場合は以下の操作は不要です。

apt update 
apt install -y ubuntu-minimal ubuntu-server

必須ではないアプリケーションの削除

サーバ用途に不要そうなアプリケーションをアンインストールしておきます。(snapdmicrok8sなどを使いたい場合は必要なので残してください。)

apt autoremove --purge -y npm snapd chromium-browser thunderbird \
ubuntu-web-launchers ubuntu-docs ubuntu-report 

apt autoremove --purge -y ubuntu-desktop ubuntu-settings \
ubuntu-artwork ubuntu-wallpapers ubuntu-sounds \
ubuntu-system-service x11proto-core-dev

Ubuntu 18.04→20.04 にアップデート

Ubuntuのシステムを20.04にアップデートします。

apt install -y update-manager
apt -y dist-upgrade && apt upgrade -y && apt autoremove -y --purge
reboot
# -f DistUpgradeViewNonInteractive をつけると、ユーザ入力不要になるので楽です。
sudo su -
do-release-upgrade -m server # -f DistUpgradeViewNonInteractive 2>&1 | tee update.log 
apt autoremove --purge -y

アップデート後、OSバージョン、およびiptablesバージョンが上がっていることを確認します。

root@jetson:~# cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.3 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.3 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal
root@jetson:~# iptables --version
iptables v1.8.4 (legacy)

Dockerのセットアップ

jetsonではdockerを使う場合、nvidiaカスタマイズされたdockerパッケージを利用します。

NVIDIA Container Toolkitの導入

コンテナランタイムにndiviaを使うためのパッケージを導入します。

https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html#docker
公式の手順に従っていきます。
sudo su -
# apt install -y curl
distribution=$(. /etc/os-release;echo $ID$VERSION_ID) && curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - && curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list

以下のようなファイルが作成されました。

/etc/apt/sources.list.d/nvidia-docker.list
deb https://nvidia.github.io/libnvidia-container/stable/ubuntu18.04/$(ARCH) /
#deb https://nvidia.github.io/libnvidia-container/experimental/ubuntu18.04/$(ARCH) /
deb https://nvidia.github.io/nvidia-container-runtime/stable/ubuntu18.04/$(ARCH) /
#deb https://nvidia.github.io/nvidia-container-runtime/experimental/ubuntu18.04/$(ARCH) /
deb https://nvidia.github.io/nvidia-docker/ubuntu18.04/$(ARCH) /

nvidia-docker2を導入します。

apt-get update && apt-get install -y nvidia-docker2

daemon.jsonの編集

次のように設定します。nvidia-docker2インストールの際にruntimesについては自動で作成されます。

daemon.json
{
    "runtimes": {
        "nvidia": {
            "path": "nvidia-container-runtime",
            "runtimeArgs": []
        }
    },
    "default-runtime": "nvidia",
    "exec-opts": [
        "native.cgroupdriver=systemd"
    ],
    "log-driver": "json-file",
    "log-opts": {
        "max-size": "100m"
    },
    "storage-driver": "overlay2"
}

設定後、docker-daemonを再起動します

systemctl restart docker

docker infoの確認

設定が変更されたかを確認します。

root@node05:~# docker info
Client:
 Context:    default
 Debug Mode: false

Server:
...(中略)...
 Storage Driver: overlay2
...(中略)...
 Cgroup Driver: systemd
 Cgroup Version: 1
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 io.containerd.runtime.v1.linux nvidia runc
 Default Runtime: nvidia
...(中略)...
 Architecture: aarch64
 CPUs: 4
 Total Memory: 1.906GiB
...(中略)...
 Insecure Registries:
  registry.neko.lab:5005
  127.0.0.0/8
 Live Restore Enabled: false

Storage Driver: overlay2Cgroup Driver: systemdが設定され、問題なさそうです。

k8sクラスタへjoin

kubeadm joinしてみます。よさそうです。

kubeadm join
[preflight] Running pre-flight checks
	[WARNING SystemVerification]: missing optional cgroups: hugetlb
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
...(中略)...
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.

クラスタの動作確認

動作の確認をしてみます。

kubectl get node
NAME       STATUS   ROLES                  AGE   VERSION
master01   Ready    control-plane,master   62d   v1.21.4
master02   Ready    control-plane,master   62d   v1.21.4
master03   Ready    control-plane,master   62d   v1.21.4
node01     Ready    <none>                 62d   v1.21.4
node02     Ready    <none>                 62d   v1.21.4
node03     Ready    <none>                 62d   v1.21.4
node05     Ready    <none>                 61m   v1.21.4
kubectl get pods -A -o wide | grep node05
kube-system      calico-node-vgvbp                          1/1     Running     3          51m     192.168.3.25     node05     <none>           <none>
kube-system      kube-proxy-vbwt6                           1/1     Running     4          51m     192.168.3.25     node05     <none>           <none>
metallb-system   speaker-87h6w                              1/1     Running     0          2m39s   192.168.3.25     node05     <none>           <none>

試しにコンテナ内から、CNIを通して外部へアクセスできるか確認してみます。以下をデプロイします。

pod-test.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: pod-test
spec:
  selector:
    matchLabels:
      app: pod-test
  template:
    metadata:
      labels:
        app: pod-test
    spec:
      containers:
        - name: pod-test
          imagePullPolicy: IfNotPresent
          image: ubuntu:20.04
          command:
            - sleep
            - infinity
Pod名を検索
% kubectl get pod
...(中略)...
pod-test-8fk5x                        1/1     Running     0          5m15s     10.244.186.211   node03   <none>           <none>
pod-test-lghtt                        1/1     Running     0          5m57s     10.244.114.2     node05   <none>           <none>
pod-test-lkw42                        1/1     Running     0          7m17s     10.244.196.175   node01   <none>           <none>
pod-test-szs9p                        1/1     Running     0          6m36s     10.244.140.72    node02   <none>           <none>
コンテナへアタッチ
% kubectl exec -it pod-test-lghtt -- /bin/bash
root@pod-test-lghtt:/# apt update
Get:1 http://ports.ubuntu.com/ubuntu-ports focal InRelease [265 kB]
...(中略)...
Get:18 http://ports.ubuntu.com/ubuntu-ports focal-security/multiverse arm64 Packages [3242 B]
Fetched 17.1 MB in 6s (2669 kB/s)
Reading package lists... Done
Building dependency tree
Reading state information... Done
1 package can be upgraded. Run 'apt list --upgradable' to see it.
root@pod-test-lghtt:/# echo $?
0

問題なくCNIで外部へ通信できるみたいです。

おわりに

本記事ではJetson nanoをkubeadm構築のクラスタに組み込むところまで実施しました。k8s上での利用(=コンテナ内でのGPU利用)については、また次の機会に実施しようと思います。

また、以前執筆した記事Jetson nanoをサーバとしてセットアップしてみた(JetCard)については、本記事にマージされたため、下書きに戻させていただきました。

余談ですが、Dockerのセットアップからクラスタへjoinは、最終的にansibleにより自動化して実施しています。タグもうっておいたので、気になる方はぜひどうぞ。

https://github.com/nkte8/labo/tree/2021-12-18-r01/ansible

備考

https://embedded.hatenadiary.org/entry/20151024/p2
2021-12-17現在、nvidiaのkernelは4.9最新(L4T Driver Package (BSP) Sources)
https://developer.nvidia.com/embedded/linux-tegra
GitHubで編集を提案

Discussion

  • サーバを止めるのではなく、リモデで接続したほうが良いな...となったので再構築中
  • Etherを使ったjetcard_v0p0p0.zipの書き込みが上手く行かないみたいです。
    • 逆に新しい方のイメージjetcard_nano-4gb-jp451.zipの方は起動を確認しました。
    • avahi-daemonは起動していてssh jetson@nano-4gb-jp451.localで接続できます。
  • 新しいバージョンのjetcardはカーネルが少し上がっていました。
    • 4.9.140-tegra4.9.201-tegra 本記事は書き直すかもしれません。
ログインするとコメントできます