YoctoLinuxで64bit RaspberryPi環境を構築し、k3sを使用する(1)

RaspberryPi 3B+を持っているので、k3s環境をつくってみようと思い立ち、いろいろ調べたのですが以下の理由でOS自体をYoctoで作成してみました。  

* raspbianは32bitOSだった。
* ubuntuは64bit版が存在するが、USBからうまく起動出来なかった。
* ArchLinuxは起動イメージ作成手順が面倒に思えた。

長くなったので(1)～(3)に分けていますが、k3s環境の構築は(2)までで終了します。
(3)はソフトウェアをパッケージ管理でソフトウェアを追加する方法になります。

# OS作成

## Yocto環境準備

1. ビルド環境(docker image)
    Yoctoのビルド環境は[公式マニュアル](https://www.yoctoproject.org/docs/?section=developer-manuals)参照  
    私は、使用しているNASにdockerが搭載されているので、これを使用してビルドする環境を作成した。  

    1. Dockerfile

        ```docker:Dockerfile
        FROM ubuntu:18.04
        ENV DEBIAN_FRONTEND noninteractive
        RUN apt-get update && apt-get dist-upgrade -y && apt-get autoremove --purge -y \
        && apt-get install -y \
        gawk wget git-core diffstat unzip texinfo gcc-multilib \
        build-essential chrpath socat cpio python python3 python3-pip python3-pexpect \
        xz-utils debianutils iputils-ping python3-git python3-jinja2 libegl1-mesa libsdl1.2-dev \
        tmux locales libtinfo-dev libncurses5-dev \
        && rm -rf /var/lib/apt/lists/*
        # Locales
        RUN dpkg-reconfigure locales \
        && locale-gen en_US.UTF-8 \
        && update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
        ENV LC_ALL   en_US.UTF-8
        ENV LANG     en_US.UTF-8
        ENV LANGUAGE en_US.UTF-8
        ```

    1. docker image 作成

        ```bash
        $ docker build [ -t ｛イメージ名｝ [ :｛タグ名｝ ] ] ｛Dockerfileのあるディレクトリ｝
        $ docker build -t yocto_build:001 .
        ```

    1. docker image 起動

        ```bash
        $ docker run -it -u $(id -u):$(id -g) -v /home:/home -w /home yoctobuild:${TAG} /bin/bash
        ```

        -vオプションは、ホストの/homeをdocker image上の/homeにマウントしてる。
        -wオプションは、起動時のカレントディレクトリを指定。  
        これらオプションで作業ディレクトリをホストと共有し、docker image終了時にファイルが破棄されない様にしている。  

1. ソース取得

    適当なディレクトリに移動して以下コマンドで取得

    ```command
    $ git clone https://git.yoctoproject.org/git/poky -b zeus
    $ git clone https://git.yoctoproject.org/git/meta-raspberrypi -b zeus
    $ git clone https://github.com/openembedded/meta-openembedded.git -b zeus
    ```

## YoctoLinux作成

### 環境変数・カレントディレクトリ設定

1. oe-init-build-env  
    pokyディレクトリ配下のoe-init-build-envを設定ファイルに使用。  
    引数に設定した文字列がカレントディレクトリになる。  
    例)

    ```command
    $ cd poky
    $ source oe-init-build-env raspberrypi
    $ pwd
    ~/poky/raspberrypi
    ```

### 設定変更

1. 基本設定・ソフトウェア

    conf/local.confを修正

    1. 機種設定  
        以下を記載

        ```conf
        MACHINE ?= "raspberrypi3-64"
        ```

    1. initのsystemd化

        以下を追記

        ```conf
        DISTRO_FEATURES_append = " systemd pam"
        VIRTUAL-RUNTIME_init_manager = "systemd"
        VIRTUAL-RUNTIME_dev_manager = "systemd"
        DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
        VIRTUAL-RUNTIME_initscripts = ""
        IMX_DEFAULT_DISTRO_FEATURES_append = " systemd"
        ```

    1. wifi bluetooth

        wifiとbluetoothを使ウカもしれないので機能FWを追加しておく

        ```conf
        COMBINED_FEATURES_append = " wifi bluetooth"
        DISTRO_FEATURES_append = " bluez5"
        ```

    1. 出力ファイルサイズの指定

        2GB SDカードに書き込めるサイズを指定

        ```conf
        IMAGE_ROOTFS_SIZE = "1787436"
        IMAGE_OVERHEAD_FACTOR = "1.0"
        ```

    1. ホストネーム設定  

        ```conf
        hostname_pn-base-files = "yoctopi3"
        ```

    1. ユーザーの追加・変更

        piユーザーを追加、パスワードとsudoサブグループを追加しシェルをbashに変更。  
        rootユーザーにパスワード追加とログイン禁止設定。  

        ```conf
        INHERIT_append = " extrausers"
        EXTRA_USERS_PARAMS = " \
            useradd -P raspberry -G sudo -s /bin/bash pi; \
            usermod -P rootuser -s /sbin/nologin root; \
        "
        ```

    1. タイムゾーン

        タイムゾーン設定で日本を指定

        ```conf
        IMAGE_INSTALL_append = " tzdata"
        DEFAULT_TIMEZONE = "Asia/Tokyo"
        ```

    1. 追加設定

        rootユーザーのホームディレクトリを指定

        ```conf
        ROOT_HOME = "/root"
        ```

        cmdline.txt の内容を編集  
        ルート領域をSDからUSBに変更(USBストレージからのbootを可能にする)  

        ```conf
        CMDLINE_remove = " root=/dev/mmcblk0p2"
        CMDLINE_append = " root=/dev/sda2"
        ```

        この設定を無効にすると、SDカード用のイメージになる。

        cgroup機能有効化

        ```conf
        CMDLINE_append = " cgroup_enable=memory cgroup_memory=1"
        ```

        GPUメモリを16MBに変更

        ```conf
        GPU_MEM = "16"
        ```

        I2CとSPIを有効化

        ```conf
        ENABLE_I2C = "1"
        ENABLE_SPI_BUS = "1"
        ```

    1. キャッシュ類のディレクトリ変更

        ```conf
        DL_DIR ?= "/home/ragnadragon/yocto_zeus/download"
        SSTATE_DIR ?= "/home/ragnadragon/yocto_zeus/sstate"
        ```

    1. 追加ソフト

        ```conf
        IMAGE_FEATURES_append = " package-management"
        IMAGE_FEATURES_append = " ssh-server-openssh"
        IMAGE_FEATURES_append = " nfs-client nfs-server"

        IMAGE_INSTALL_append = " bash sudo"
        IMAGE_INSTALL_append = " openssh openssh-sftp-server"
        IMAGE_INSTALL_append = " acpid acpitool"
        IMAGE_INSTALL_append = " vim vim-tiny"
        IMAGE_INSTALL_append = " avahi-daemon"
        IMAGE_INSTALL_append = " util-linux usbutils"
        IMAGE_INSTALL_append = " net-tools iproute2"
        IMAGE_INSTALL_append = " wget curl python3-pip"
        IMAGE_INSTALL_append = " i2c-tools python-smbus bridge-utils hostapd"
        IMAGE_INSTALL_append = " linux-firmware-rpidistro-bcm43455 wpa-supplicant bluez5"
        ```

    1. コメント無し全体

        ```conf:local.conf
        MACHINE ?= "raspberrypi3-64"
        MACHINE ??= "qemux86"
        DISTRO_FEATURES_append = " systemd pam"
        VIRTUAL-RUNTIME_init_manager = "systemd"
        VIRTUAL-RUNTIME_dev_manager = "systemd"
        DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
        VIRTUAL-RUNTIME_initscripts = ""
        IMX_DEFAULT_DISTRO_FEATURES_append = " systemd"
        COMBINED_FEATURES_append = " wifi bluetooth"
        DISTRO_FEATURES_append = " bluez5"
        IMAGE_ROOTFS_SIZE = "1807436"
        IMAGE_OVERHEAD_FACTOR = "1.0"
        hostname_pn-base-files = "yoctopi3"
        INHERIT_append = " extrausers"
        EXTRA_USERS_PARAMS = " \
            useradd -P raspberry -G sudo -s /bin/bash pi; \
            usermod -P rootuser -s /sbin/nologin root; \
        "
        IMAGE_INSTALL_append = " tzdata"
        DEFAULT_TIMEZONE = "Asia/Tokyo"
        ROOT_HOME = "/root"
        CMDLINE_remove = "root=/dev/mmcblk0p2"
        CMDLINE_append = "root=/dev/sda2"
        CMDLINE_append = " cgroup_enable=memory cgroup_memory=1"
        GPU_MEM = "16"
        ENABLE_I2C = "1"
        ENABLE_SPI_BUS = "1"
        DL_DIR ?= "~/download"
        SSTATE_DIR ?= "~/sstate"
        DISTRO ?= "poky"
        PACKAGE_CLASSES ?= "package_rpm"
        EXTRA_IMAGE_FEATURES ?= "debug-tweaks"
        USER_CLASSES ?= "buildstats image-mklibs image-prelink"
        PATCHRESOLVE = "noop"
        BB_DISKMON_DIRS ??= "\
            STOPTASKS,${TMPDIR},1G,100K \
            STOPTASKS,${DL_DIR},1G,100K \
            STOPTASKS,${SSTATE_DIR},1G,100K \
            STOPTASKS,/tmp,100M,100K \
            ABORT,${TMPDIR},100M,1K \
            ABORT,${DL_DIR},100M,1K \
            ABORT,${SSTATE_DIR},100M,1K \
            ABORT,/tmp,10M,1K"
        PACKAGECONFIG_append_pn-qemu-system-native = " sdl"
        CONF_VERSION = "1"
        IMAGE_FEATURES_append = " package-management"
        IMAGE_FEATURES_append = " ssh-server-openssh"
        IMAGE_FEATURES_append = " nfs-client nfs-server"
        IMAGE_INSTALL_append = " bash sudo"
        IMAGE_INSTALL_append = " openssh openssh-sftp-server"
        IMAGE_INSTALL_append = " acpid acpitool"
        IMAGE_INSTALL_append = " vim vim-tiny"
        IMAGE_INSTALL_append = " avahi-daemon"
        IMAGE_INSTALL_append = " util-linux usbutils"
        IMAGE_INSTALL_append = " net-tools iproute2"
        IMAGE_INSTALL_append = " wget curl python3-pip"
        IMAGE_INSTALL_append = " i2c-tools python-smbus bridge-utils hostapd"
        IMAGE_INSTALL_append = " linux-firmware-rpidistro-bcm43455"
        IMAGE_INSTALL_append = " wpa-supplicant iw bluez5"
        ```

1. レイヤー

    bblayers.confの設定  

    1. レイヤー追加  

        ダウンロードした各ソースを登録

        ```bash
        $ bitbake-layers add-layer ../../meta-raspberrypi
        $ bitbake-layers add-layer ../../meta-openembedded/meta-oe
        $ bitbake-layers add-layer ../../meta-openembedded/meta-networking
        $ bitbake-layers add-layer ../../meta-openembedded/meta-filesystems
        $ bitbake-layers add-layer ../../meta-openembedded/meta-webserver
        $ bitbake-layers add-layer ../../meta-openembedded/meta-multimedia
        $ bitbake-layers add-layer ../../meta-openembedded/meta-initramfs
        $ bitbake-layers add-layer ../../meta-openembedded/meta-perl
        $ bitbake-layers add-layer ../../meta-openembedded/meta-python
        ```

    1. カスタムレイヤー追加

        初期状態でデフォルトから変更したい設定などをレイヤー・レシピ化

        1. 初期設定

            1. レイヤー作成

                ```bash
                bitbake-layers create-layer ../sources/meta-local-bb
                ```

            1. レイヤー追加

                ```bash
                bitbake-layers add-layer ../sources/meta-local-bb
                ```

        1. 基本設定(base-files)

            1. 差分ファイル作成

                基本設定に対する以下の変更を行う  
                * /usr/local以下のディレクトリを作成する
                * ユーザーデフォルトプロファイルを変更する  
                    標準ではsbin系がPATHに設定されないことへの対処  
                    標準ではsudoのコマンドをTABで補完出来ないことへの対処

                ```bash
                $ mkdir -p ../sources/meta-local-bb/recipes-core/base-files/files
                ```

                ---

                ../sources/meta-local-bb/recipes-core/base-files/base-files_%.bbappend

                ```conf:base-files_%.bbappend
                FILESEXTRAPATHS_prepend := "${THISDIR}/files:"

                SRC_URI += " \
                    file://dot.profile \
                "

                do_install_append () {
                    install -d ${D}/usr/local/bin
                    install -d ${D}/usr/local/etc
                    install -d ${D}/usr/local/include
                    install -d ${D}/usr/local/lib
                    install -d ${D}/usr/local/sbin
                    install -d ${D}/usr/local/share
                    install -d ${D}/usr/local/src
                    install -d ${D}${sysconfdir}/skel
                    install -m 0755 ${WORKDIR}/dot.profile ${D}${sysconfdir}/skel/.profile
                }
                ```

                ---

                ../sources/meta-local-bb/recipes-core/base-files/files/dot.profile

                ```conf:dot.profile
                # ~/.profile: executed by Bourne-compatible login shells.

                if [ -f ~/.bashrc ]; then
                  . ~/.bashrc
                fi

                # path set by /etc/profile
                export PATH="/sbin:/usr/sbin:/usr/local/sbin:$PATH"

                complete -cf sudo

                # Might fail after "su - myuser" when /dev/tty* is not writable by "myuser".
                mesg n 2>/dev/null
                ```

        1. sudo設定(sudo)

            1. 差分ファイル作成

                標準ではrootユーザーのみsudo可能になっている  
                /etc/sudoers.d/配下にファイルを追加し、admin/sudoグループにもsudoを許可

                ```bash
                $ mkdir -p ../sources/meta-local-bb/recipes-core/sudo/files
                ```

                ---

                ../sources/meta-local-bb/recipes-core/sudo/sudo_%.bbappend

                ```conf:sudo_%.bbappend
                FILESEXTRAPATHS_prepend := "${THISDIR}/files:"

                SRC_URI += " \
                    file://01-sudoer \
                "

                do_install_append() {
                    install -d ${D}${sysconfdir}/sudoers.d
                    install -m 0644 ${WORKDIR}/01-sudoer ${D}${sysconfdir}/sudoers.d/01-sudoer
                }
                ```

                ---

                ../sources/meta-local-bb/recipes-core/sudo/files/01-sudoer

                ```conf:01-sudoer
                # Members of the admin group may gain root privileges
                %admin ALL=(ALL) ALL

                # Allow members of group sudo to execute any command
                %sudo   ALL=(ALL) ALL
                ```

### ビルド

1. ビルドするイメージ

    raspberry piで動作するイメージは以下の中から選ぶ  
      * CUI

        core-image-base
      * GUI(X11)

        core-image-sato
      * GUI(Wayland/Weston)

        core-image-weston

    ※起動確認以降の手順はcore-image-baseで進める。  
    そのほかにcore-image-minimalを選択出来るが、カーネルモジュールが取り込まれず、GPIOなどの使用に支障が出る模様。  
    conf/local.confに以下の設定を追加すればおおよそのモジュールは取り込まれる。

    ```conf
    MACHINE_ESSENTIAL_EXTRA_RRECOMMENDS += "kernel-modules"
    ```

    core-image-baseと差分がほぼなくなるので積極的に選択する理由はない。  
    また、core-image-satoはコンポーネントgstreamerの依存パッケージ「faad2」のライセンスがcommercialとなっている、このライセンスを許容するためにconf/local.confに以下の設定を追加する必要がある。  

    ```conf
    LICENSE_FLAGS_WHITELIST += "commercial"
    ```

    ※core-image-westonはGUIが起動しなかった。
1. core-image-baseをビルド  
    以下コマンドでビルドを実施

    ```bash
    $ bitbake core-image-base
    ```

    以下のイメージが作成される

    ```bash
    $ ls tmp/deploy/images/raspberrypi3-64
    ・・・
    core-image-base-raspberrypi3-64.rpi-sdimg   (シンボリックリンク)
    core-image-base-raspberrypi3-64-<yyyyMMddHHmmss>.rootfs.rpi-sdimg
    ・・・
    ```

1. core-image-satoをビルド  
    以下コマンドでビルドを実施

    ```bash
    $ bitbake core-image-sato
    ```

    以下のイメージが作成される

    ```bash
    $ ls tmp/deploy/images/raspberrypi3-64
    ・・・
    core-image-sato-raspberrypi3-64.rpi-sdimg   (シンボリックリンク)
    core-image-sato-raspberrypi3-64-<yyyyMMddHHmmss>.rootfs.rpi-sdimg
    ・・・
    ```

1. core-image-westonをビルド  
    以下コマンドでビルドを実施

    ```bash
    $ bitbake core-image-weston
    ```

    以下のイメージが作成される

    ```bash
    $ ls tmp/deploy/images/raspberrypi3-64
    ・・・
    core-image-weston-raspberrypi3-64.rpi-sdimg   (シンボリックリンク)
    core-image-weston-raspberrypi3-64-<yyyyMMddHHmmss>.rootfs.rpi-sdimg
    ・・・
    ```

## 起動確認

1. [balenaEtcher](https://www.balena.io/etcher/)やddコマンドを使用して作成したイメージをメディアに書き込む。

    USBからのブート用の場合はUSBメモリ・USB-SSDなどUSB接続のストレージに、SDカード用イメージであればSDカードに書き込む。  
1. Raspberry Piにメディアを接続し、有線LANを接続して電源を投入する。  
1. 起動が完了するまで待って、sshコマンドでアクセスする。  

    ```bash
    ssh pi@yoctopi3.local
    ```

    pi@yoctopi3.localの部分は、conf/local.confで設定したパラメータを使い、「<ユーザー名>@<ホスト名>.local」とする。  
    (windows10 1809以降であればopensshが取り込まれているため、powershellやコマンドプロンプトからでも上記コマンドでアクセス可能)  
    powershellで実行すると以下の様になる。

    ```powershell
    PS C:\Users\ragna> ssh pi@yoctopi3.local
    The authenticity of host 'yoctopi3.local (2400:4053:c081:2d00:ba27:ebff:fe26:5877)' can't be established.
    ECDSA key fingerprint is SHA256:Wvo4trcK8bp79pVZkl3JASKZI829Rt6k4kfQaqNnM/U.
    Are you sure you want to continue connecting (yes/no)?    <-yesを入力
    Warning: Permanently added 'yoctopi3.local,2400:4053:c081:2d00:ba27:ebff:fe26:5877' (ECDSA) to the list of known hosts.
    pi@yoctopi3.local's password:    <-パスワード[raspberry]を入力
    yoctopi3:~$
    ```

1. sudoが有効である事とタイムゾーンがJSTになっている事を確認  
    以下コマンドで確認

    ```bash
    yoctopi3:~$ sudo date
    Password:            <-パスワード[raspberry]を入力
    Thu Mar 26 00:56:01 JST 2020    <-現在時刻がJSTで表示される事を確認
    yoctopi3:~$
    ```

    現在時刻は「systemd-timesyncd」サービスによって、NTPで時刻合わせが行われる。

1. ネットワーク設定変更
    固定IPアドレス設定とホストネームの変更方法は以下になる
    1. 固定IP化
        systemd-networkのデフォルト設定ファイルを/etc/systemd/network/にコピーして編集
        [Network]部分を修正

        ```bash
        $ sudo cp /lib/systemd/network/80-wired.network /etc/systemd/network/
        $ sudo vi /etc/systemd/network/80-wired.network
        [Match]
        Name=en* eth*
        KernelCommandLine=!nfsroot

        [Network]
        DNS=192.168.25.1
        Address=192.168.25.99/24
        Gateway=192.168.25.1

        [DHCP]
        RouteMetric=10
        ClientIdentifier=mac
        ```

        設定変更を読み込み、サービスを再起動

        ```bash
        $ sudo systemctl daemon-reload
        $ sudo systemctl restart systemd-networkd
        ```

        sshが切断されるので、再接続する。
        ネットワークを確認

        ```bash
        $ ip a
        ・・・
        2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
            link/ether b8:27:eb:26:58:77 brd ff:ff:ff:ff:ff:ff
            inet 192.168.25.99/24 brd 192.168.25.255 scope global eth0
               valid_lft forever preferred_lft forever
            inet6 2400:4053:c081:2d00:ba27:ebff:fe26:5877/64 scope global dynamic mngtmpaddr noprefixroute
               valid_lft 2591944sec preferred_lft 604744sec
            inet6 fe80::ba27:ebff:fe26:5877/64 scope link
               valid_lft forever preferred_lft forever
        ・・・
        ```

    1. ホスト名変更
        hostnamectlコマンドを使用する
        「yoctopi3」から「yoctopi3-master」に変更

        ```bash
        yoctopi3:~$ hostname
        yoctopi3
        yoctopi3:~$ sudo hostnamectl set-hostname yoctopi3-master
        yoctopi3:~$ hostname
        yoctopi3-master
        ```

        シェルを再接続

        ```bash
        yoctopi3-master:~$ hostname
        yoctopi3-master
        ```