FPGA K3sクラスターを作成する Part1 (準備・Server Node/クライアント編)

Kubernetes\* 上でコンテナアプリケーションを実行するためにはまずクラスターと呼ばれるものを構築する必要があります。([参考][LINK_K8s_Component])
今回と次回の記事を通じて、Terasic社製のDE10-Nanoを用いてKubernetesのディストビューションの１つであるK3s\*を用いてFPGAクラスターを構築して最後にFPGAのアップデートを試してみます。
> 注意事項: 本記事では、あくまでクラスター構築に必要な部分のみしか解説しないため、細かい用語等には触れません。

KubernetesとAzure IoT Edge\*はコンテナベースの開発という観点のみに着目すると非常に親和性が高いため、
Intel®︎ Developer Zone(IDZ)に公開されているドキュメント[(英語版)][LINK_IDZ], [(日本語(ページ内中央左にあるチュートリアル・モジュール1-4))][LINK_IDZ_JP]をよく引用しますので、合わせて読むことを推奨します。

## クラスター構成

著者環境

+ Host OS : MacOS Catalina 10.15.7
+ Guest OS(Server Node) : Ubuntu 18.04.5
+ DE10-Nanos' OS(Agent Node) : Ubuntu 16.04.6
+ K3s version : v1.20.4+k3s1 (838a906a)

![cluster-architecture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698105/87123b86-e3ce-d257-e64c-49370e12d648.png)

クラスター全体図はこのようになっています。Host OSの環境をならべく汚さないようにする+様々なOSから利用できるようにするため、Server Node(Kubernetesでは一般的にMaster Nodeとも言います)はVMを立ててその中に構築しました。
Server Nodeとして利用するVMのOSとしては、Ubuntu18.04を利用していますが、K3s自体にはOS Dependencyがないそうなので、どのOSを使っても問題ありません。
Host OS側には、Server Nodeにアクセスするためのクライアントとして`kubectl`のみをインストールします。
Agent Node(Kubernetesでは一般的にWorker Nodeとも言います)には今回はTerasic社製のDE10-Nanoを利用します。
OSイメージとしては、同社が公開しているコンテナがすでに使える状態になっている、`DE10-Nano-Cloud_native.img`を使います。

コンテナのイメージ格納庫として[Amazon ECR*(Amazon Elastic Container Registry)][LINK_ECR]というAWS(Amazon Web Service)のサービスを利用しました。
開発用に使うには、こういったクラウドのサービスを活用してプライベートレポジトリを作成するのが最も簡単なので、私はよく利用しています。
Microsoft AzureやGCP(Google Cloud Platform)にも同様なサービスがあるので、こちらを利用するのもよいでしょう。

*Note: 今後コマンドを実行する際は、どこから実行したかをわかりやすくするため前に(devPC=Client),(Guest=Server Node=VM),(de10nano=Agent Node)の三種類を付属します。*
> 本記事はdevPC/Guestに焦点を当てています。de10nano側の構築に関しては、[後編][LINK_Part2]をご覧ください。
> 今回の記事は前準備の内容が多いため、FPGAの話はあまり出てきませんのでご承知ください。

## Server Node準備（Development PC)
お使いのPCが普段からLinuxかつIPアドレスをstaticで運用している場合は特にこのステップは不要です。
それ以外の場合は下記３つの手順を必要に応じてカスタムしながら行ってください。
1. VirtualBoxのインストール
2. Ubuntu18.04のインストール
3. VMの設定変更(VirtualBoxの設定 / IPアドレスの固定 / Hostnameの変更(Optional))

私はMacを開発用PCとして普段から利用していますので、その際の手順を説明します。

> Macの場合、multipassなども考えられますが、multipassは外部からVMに直接アクセスできないなどNetwork Bridgeの周りが不調の場合も多いため、今回はVirtualBoxを利用しています。

### VirtualBoxのインストール

[Oracle VM VirtualBox Official Site][LINK_VirtualBox_Official_Page]にアクセスしお好きなバージョンをダウンロード・インストールします。
インストール後にVirtualBoxを起動し、新規を押した時に、任意のLinux64bitsOSが指定できれば完了です。

> Windowsの場合、Securityの設定などで64bitsが指定できない場合があります。その時はOSやBIOSの設定を見直してください。

### Ubuntu18.04のインストール

[Ubuntu 18.04 Release Page][LINK_Ubuntu1804_Release]からUbuntu18.04のISOをダウンロードしてインストールします。Server, Desktopどちらでも構いません。私はServerを使いました。

新規作成時に設定のカスタマイズは必要ありませんが、もしPCに余裕があるのであればメモリは少し増やしておくとよいかもしれません。Diskは可変長にしておくことをお勧めします。
ユーザー名やパスワードについてもお好きなものを利用できます。
ISOを抜いてUbuntuが起動/設定したユーザーでログインできれば完了です。

### VMの設定変更(VirtualBoxの設定 / IPアドレスの固定 / Hostnameの変更(Optional))

1. VirtualBoxの設定
    一度起動したら一度VMを落とします。落としたら該当のVM設定を開きます。ネットワークのカテゴリに行き、割り当てをブリッジアダプターに変更します。（デフォルトはNAT）
    名前は使っているホストのアダプタの名前を指定します。（特に問題なければデフォルト）
　　　　　　　　![network-bridge.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698105/b9a37137-e52e-0ba0-ef54-0b56532eb764.png)


    変更を保存したら、VMを起動し、ネットワークに接続されるか再確認します。

2. IPアドレスの固定
    接続されるのが確認できたら次は、IPアドレスの固定を行います。
    この作業をしないと、VMを再起動するたびServer NodeのIPアドレスが変わる可能性があり、Agentと通信できなくなるのを防ぐためです。
    先にも述べたように、設定内容に関しては、基本的にはルータの設定に依存します。
    
    > ルータ側にも利用するIPをDHCPから外さない場合、ネットワーク環境に影響が出る可能性があります。

    ```
    (Guest)$ ip addr
    ```
    ファイルの設定はUbuntu18.04 Desktopであれば設定のGUI上から行えますし、Serverの場合には、netplanというサービスを利用します。例えば下記です。

    ```bash
    (Guest)$ sudo vi /etc/netplan/50-cloud-init.yaml #Edit
    (Guest)$ cat /etc/netplan/50-cloud-init.yaml #Check
    # This file is generated from information provided by
    # the datasource.  Changes to it will not persist across an instance.
    # To disable cloud-init's network configuration capabilities, write a file
    # /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following:
    # network: {config: disabled}
    network:
        ethernets:
            enp0s3:
                addresses: [192.168.100.100/24]
                gateway4: 192.168.100.1
                nameservers:
                    addresses: [192.168.100.1]
                    search: []
                optional: true
                #dhcp4: true
        version: 2
    (Guest)$ sudo netplan apply #Enable the new setting
    ```
3. (Optional) Hostnameの変更
    Hostnameの変更はk3s導入時にNode名を被らないようにするためです。
    `/etc/hosts`と`/etc/hostname`を編集して、VMを再起動します。
    
    *Note: 現在はAgent側の構築時にNode名を自由に指定できるオプションが追加されているため、必ずしも行う必要はありません。*

    例では、k3sserverとしました。

    ```
    (Guest)$ sudo vi /etc/hostname #Edit
    (Guest)$ sudo vi /etc/hosts #Edit
    (Guest)$ cat /etc/hostname #Check
    k3sserver
    (Guest)$ cat /etc/hosts #Check
    127.0.0.1 localhost
    127.0.1.1 k3sserver
    ```

## 開発環境準備

今回はFPGAノードへのコンテナのビルドおよびデプロイを試すので、それらに必要なツールを準備します。

### Docker Buildxのインストール

DE10-Nanoに乗っているCPUのアーキテクチャはArm32v7のため、PCでコンテナビルドをするためには、Dockerのクロスビルド機能、Docker Buildxが必要です。Dockerを通常通りインストールしたのち、[公式ガイド][LINK_Docker_buildx]に従って、インストールします。
    
Docker BuildxはMacおよびLinuxが前提ですが、Windowsでも。WSL2を[Docker Backend][LINK_WSL_Docker_Backend]として使うとできるそうです。（筆者はWindows環境がないため未検証)　Linux環境の資料とはなってしまいますが、[英語版][LINK_Buildx_IDZ], [日本語版][LINK_Buildx_IDZ_JP]にも同様の手順が書いてあるのでよろしければどうぞ。

### Container Registryの準備

先にも述べたように、今回はコンテナイメージの格納庫(Container Registry)として、Amazon ECR*(Amazon Elastic Container Registry)を利用します。Microsoft AzureやGCP,その他オンプレミスなどのサービスを利用する方はそれぞれのやり方に従ってください。
本節では、コンテナレジストリを作成するところまでですが、後のKubernetesのsecretを作成するところでも手順が異なる可能性がありますので、適宜検索してください。
また、本節は特に覚書的な要素が強いため、より詳しい内容が知りたい方は他の記事等も同時に参考にすることを推奨します。

さて、１からECRを構築するためには、管理者(Root User)からの観点とIAMユーザーからの観点の２つが必要です。
AWSでは、実際に何かを行う際は、IAM(Identity and Access Management)ユーザーを使うことを推奨していますので、その流儀にのっとります。
[AWSの記事][LINK_ECR_Login]および[こちらのQiitaの記事][LINK_ECR_IAM]はとても参考になりました。

> やり方は、2021/7月時点のものであり、変更になっている可能性もあります。

#### 管理者向け手順
前提として、すでにAWSには登録し利用するリージョンも決まっている状態でスタートします。

1. アクセス用のIAMユーザの作成  
    ECRに対してアクセスするIAMユーザーを作成します。既存のユーザーを使い回す場合はこの手順は不要です。
    AWSにログイン後、[IAM Service][LINK_IAM_in_AWS]にアクセス(上部のsearchboxでECRで検索など)->`Add User` -> `Programmatic access`にチェック、`AWS Management Console`(Optional)でチェックします。
    Permission等は後からでも登録できるため、今は特に何も設定せずスキップします。
    作成後、Userのページにいき、`User ARN`(arn:aws:iam:XXXXXXXXXX)をコピーしておきます。


2. レポジトリの作成
    アカウントが作成できたら、ECRのページへアクセス(上部のsearchboxでECRで検索など)->`Create repository`->`Visibility settings`を`Private`->お好みの名前をつけます。
    その他の設定は必要に応じて設定してください。私は初期設定のまま行っています。


3. レポジトリのPermissionの変更
    リストからレポジトリをクリックした後、左のサイドバーの中にある`Permissions`を選択します。
　　　　　　![ecr-permission.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698105/cef6cba7-5c8b-4cfd-0f6c-8544b6c350a8.png)

    右上の`Edit policy JSON`を選択し、下記のJSONをコピペ後、Statement:Principal:AWSの下のARNを先ほどのARNに変更します。

    ```JSON
    {
    "Version": "2008-10-17",
    "Statement": [
        {
        "Sid": "Test",
        "Effect": "Allow",
        "Principal": {
            "AWS": [
            "arn:aws:iam::XXXXXXX"
            ]
        },
        "Action": [
            "ecr:BatchCheckLayerAvailability",
            "ecr:BatchGetImage",
            "ecr:CompleteLayerUpload",
            "ecr:DescribeImages",
            "ecr:DescribeRepositories",
            "ecr:GetDownloadUrlForLayer",
            "ecr:InitiateLayerUpload",
            "ecr:PutImage",
            "ecr:UploadLayerPart"
        ]
        }
    ]
    } 
    ```
    [公式のガイド][LINK_ECR_Login]の`DescribeRepositories`や`DescribeImages`を除いた７つでも実行自体はできますが、これらの権限がないと、Web Browserからは確認できなかったので、つけておくことをお勧めします。
    問題なく解釈できれば、statementが追加されます。

#### User向け

0. IAMユーザ/レポジトリ情報の共有(Access Keys)
    情報をもらいます。以下の情報が必要です。
    - IAM Username(Optional): Web Browser等でログインする時に必要
    - IAM Password(Optional): Web Browser等でログインする時に必要
    - AWS Access Key ID
    - AWS Secret Access Key
    - ECR Repository URL

1. AWS CLIのインストール
    [AWSの公式サイト][LINK_AWS_CLI]からダウンロードしたり、お好みのパッケージマネージャーを使ってインストールしてください。
    Macであれば次のコマンド。

    ```
    (devPC) $ brew install awscli
    ```

2. Credentialの確保 / Docker Loginテスト

    ```
    (devPC) $ aws configure
    AWS Access Key ID [None]:
    AWS Secret Access Key [None]:
    Default region name [None]:
    Default output format [None]: json
    ```
    の順番に表示されるので、適宜 `0. IAMユーザ/レポジトリ情報の共有(Access Keys)`で共有された情報に従って登録してください。
    もし、すでにアカウントを持っている人は、[次に従って][LINK_AWS_Named_Profile]適宜プロファイルを編集してください。

    登録できれば、次のコマンドで、レジストリのログインに必要なCredentialを取得できます。

    ```
    (devPC) $ aws ecr get-login-password
    eyJwYXl...(omit)
    ```

    *Note: このCredentialは12時間のみ有効になっているようなので、適宜アップデートが必要なようです。*
    試しにコンテナレジストリにビルド済みのイメージをPushするやり方について記述します。
    イメージのビルドに関しては、後述のビルドイメージ作成というセクションで説明します。

    ```
    (devPC) $ aws ecr get-login-password | docker login --username AWS --password-stdin <AWS account ID>.dkr.ecr.<region>.amazonaws.com
    Login Succeeded
    (devPC) $ docker tag <original> <AWS account ID>.dkr.ecr.<region>.amazonaws.com/<repository name>:<tag>
        #e.g. $ docker tag 058 309190452075.dkr.ecr.us-east-2.amazonaws.com/kubernetes-demo-image:testv0
    (devPC) $ docker push <AWS account ID>.dkr.ecr.<region>.amazonaws.com/kubernetes-demo-image:testv0
    (devPC) $ aws ecr describe-images --repository-name <repository name>
        #e.g. $ aws ecr describe-images --repository-name kubernetes-demo-image
        imageDetails:
        - artifactMediaType: application/vnd.docker.container.image.v1+json
            imageDigest: sha256:343e914b63d7c717396c3c75427f4badc64fa23fa64234cc5254dea87c4aa662
            imageManifestMediaType: application/vnd.docker.distribution.manifest.v2+json
            imagePushedAt: '2021-03-31T14:14:27+09:00'
            imageSizeInBytes: 40383911
            imageTags:
            - testv0
            registryId: '309190452075'
            repositoryName: kubernetes-demo-image
    ```

    これで完成したイメージをPushできるようになりました。ブラウザー上でも確認できます。
    ![ecr-list-images.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698105/84bb659d-6cae-73fa-8faa-2af3ecfe394e.png)

## K3sインストール(Server Node)
今回は公式インストールに従って行いますが、[k3sup][LINK_TO_K3sup]というユーティリティーツール現在ではリリースされています。こちらを用いるのもよいでしょう。
前編ではk3s serverをServer NodeとなるGuest(VM)にインストールします。後編ではAgent Nodeを準備します。
この部分は基本的に[K3sの公式インストール][LINK_K3S_INSTALL]に従います。
下記のコマンドを打ち込むと最新バージョンのK3sがインストールされます。また、自動的に、k3s-serverが実行されるためこれで十分です。

```
(Guest) $ curl -sfL https://get.k3s.io | sh -
```

念のため状態を確認します。

```
(Guest) $ sudo systemctl status k3s.service 
● k3s.service - Lightweight Kubernetes
    Loaded: loaded (/etc/systemd/system/k3s.service; enabled; vendor preset: enabled)
    Active: active (running) since Wed 2021-03-31 14:48:35 JST; 6min ago
        Docs: https://k3s.io
    Process: 918 ExecStartPre=/sbin/modprobe overlay (code=exited, status=0/SUCCESS)
    Process: 906 ExecStartPre=/sbin/modprobe br_netfilter (code=exited, status=0/SUCCESS)
    Main PID: 931 (k3s-server)
        Tasks: 31
    CGroup: /system.slice/k3s.service
            ├─ 931 /usr/local/bin/k3s server
            └─1252 containerd 
(Guest) $ sudo k3s kubectl get nodes
NAME                STATUS     ROLES                  AGE     VERSION
k3sserver           Ready      control-plane,master    5m     v1.20.4+k3s1
```

## クライアントのkubectl設定
ここまでで、Server NodeからはKubernetesのコントロールプレーンにアクセスできるようになりました。しかしながら毎回GUIを立ち上げたり、SSHなどでログインするのは面倒です。
そこで、Contextをコピーすることでdevelopment PCから直接コントロールプレーンへとアクセスできるようにします。

まずは、[公式ガイド][LINK_KUBECTL_INSTALL]を参考にでkubectlをインストールします。
Macであれば、下記のコマンド。

```
(devPC) $ brew install kubectl
```

続いて、VM内で参照しているContextファイルをコピーします。ファイルは`/etc/rancher/k3s/k3s.yaml`です。このファイルはrootユーザしか参照権限がないので、注意しましょう。

```
(Guest) $ sudo cp /etc/rancher/k3s/k3s.yaml $HOME
(Guest) $ sudo chmod 644 $HOME/k3s.yaml
(devPC) $ scp (VM):$HOME/k3s.yaml ~/.kube/
```

コピ-してきたファイルの`clusters:cluster:server`(おそらく５行目)はローカルホスト(127.0.0.1)を参照するようになっているので、ここは、先の`K3S_URL`と同様に、Server NodeのIPアドレス+ポート番号に変更します。

```
Before : server : https://127.0.0.1:6443
After  : server : https://192.168.100.100:6443
```

kubectlが参照するファイルは、`$HOME/.kube/config` です。そのまま上書きしてしまうと、別のコンテキスト情報が消えてしまうので、次のようにすることで回避します。

```bash
(devPC) $ mv $HOME/.kube/config $HOME/.kube/config-backup #Backup
(devPC) $ KUBECONFIG=$HOME/.kube/config-backup:$HOME/.kube/k3s.yaml kubectl config view --flatten > $HOME/.kube/config
(devPC) $ kubectl config get-contexts
CURRENT   NAME       CLUSTER    AUTHINFO   NAMESPACE
            default    default    default
*         minikube   minikube   minikube   default
(devPC) $ kubectl config use-context default #Switch default context
(devPC) $ kubectl get nodes
NAME                STATUS     ROLES                  AGE     VERSION
k3sserver           Ready      control-plane,master   47m     v1.20.4+k3s1
```

これで、development PCからコントロールプレーンにアクセスできるようになりました。

## ECRの認証情報登録
認証情報なしにKubernetesのクラスターはプライベートレポジトリにアクセスできません。
そのため、認証情報をSecretとして登録しておくことで、ECR内にアップロードされたコンテナイメージをPullできるようになります。  
今回は使いませんがテストもかねてここで登録してしまいましょう。

```bash
#Get username and password from AWS CLI
AWS_PASSWORD=`aws ecr get-login-password`
(devPC) $ kubectl create secret docker-registry aws \
    --docker-server=<AWS account ID>.dkr.ecr.<region>.amazonaws.com \
    --docker-username=AWS \
    --docker-password=$AWS_PASSWORD \
    --docker-email=<your-email>
#kubectl get secrets aws -o yaml #Check
```

これで前準備となるServer Nodeの準備およびクライアントの準備が終了しました。

[後編][LINK_Part2]に続く

## References

+ [Kubernetesのコンポーネント][LINK_K8s_Component]
+ [Build, Deploy, and Manage your FPGA-Based IoT Edge Applications using Microsoft* Azure][LINK_IDZ]
+ [インテルのエッジセントリック FPGA ソリューション][LINK_IDZ_JP]
+ [Amazon ECR | Docker Container Registry | Amazon Web Services][LINK_ECR]
+ [Oracle VM VirtualBox][LINK_VirtualBox_Official_Page]
+ [Ubuntu 18.04 Release][LINK_Ubuntu1804_Release]
+ [Docker Buildx | Docker Documentation][LINK_Docker_buildx]
+ [Docker Desktop WSL 2 backend | Docker Documentation][LINK_WSL_Docker_Backend]
+ [Build an Azure* Container-based Application using Visual Studio Code][LINK_Buildx_IDZ]
+ [FPGA クラウド・コネクティビティー・キット・チュートリアル・モジュール 2][LINK_Buildx_IDZ_JP]
+ [Amazon ECR イメージリポジトリ内のイメージをプッシュまたはプルするためのセカンダリアカウントを許可するにはどうすればよいですか?][LINK_ECR_Login]
+ [Amazon ECR に特定 IAM ユーザからのみのアクセスを許可][LINK_ECR_IAM]
+ [IAM dashboard][LINK_IAM_in_AWS]
+ [AWS Command Line Interface][LINK_AWS_CLI]
+ [AWS Named profiles][LINK_AWS_Named_Profile]
+ [alexellis/k3sup: bootstrap Kubernetes with k3s over SSH][LINK_TO_K3sup]
+ [Rancher Docs: Installation][LINK_K3S_INSTALL]
+ [Install and Set UP kubectl][LINK_KUBECTL_INSTALL]

[LINK_Part2]:https://qiita.com/hikawai/items/6791dde71e63116fa95c
[LINK_K8s_Component]:https://kubernetes.io/ja/docs/concepts/overview/components/
[LINK_IDZ]:https://software.intel.com/content/www/us/en/develop/articles/build-and-deploy-iot-edge-applications-using-azure.html
[LINK_IDZ_JP]:https://www.intel.co.jp/content/www/jp/ja/products/programmable/edge-centric-fpga-solutions.html
[LINK_ECR]:https://aws.amazon.com/ecr/
[LINK_VirtualBox_Official_Page]: https://www.virtualbox.org/
[LINK_Ubuntu1804_Release]: https://releases.ubuntu.com/18.04/
[LINK_Docker_buildx]: https://docs.docker.com/buildx/working-with-buildx/
[LINK_WSL_Docker_Backend]:https://docs.docker.com/docker-for-windows/wsl/
[LINK_Buildx_IDZ]:https://software.intel.com/content/www/us/en/develop/articles/build-an-azure-container-based-application-using-visual-studio.html#inpage-nav-3
[LINK_Buildx_IDZ_JP]:https://www.intel.co.jp/content/www/jp/ja/products/docs/programmable/cloud-connectivity-kit-tutorial-module-2.html
[LINK_ECR_Login]: https://aws.amazon.com/jp/premiumsupport/knowledge-center/secondary-account-access-ecr/?nc1=h_ls
[LINK_ECR_IAM]:https://qiita.com/pottava/items/2a5a2ba0d4f186c6b78f
[LINK_IAM_in_AWS]:https://console.aws.amazon.com/iam/home
[LINK_AWS_CLI]: https://aws.amazon.com/cli/
[LINK_AWS_Named_Profile]: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html 
[LINK_TO_K3sup]: https://github.com/alexellis/k3sup
[LINK_K3S_INSTALL]: https://rancher.com/docs/k3s/latest/en/installation/
[LINK_KUBECTL_INSTALL]: https://v1-18.docs.kubernetes.io/docs/tasks/tools/install-kubectl/


## Notices & Disclaimers
No product or component can be absolutely secure. 
Your costs and results may vary. 

Copies of documents which have an order number and are referenced in this document may be obtained by calling 1-800-548-4725 or visiting <http://www.intel.com/design/literature.htm>. ​

Intel disclaims all express and implied warranties, including without limitation, the implied warranties of merchantability, fitness for a particular purpose, and non-infringement, as well as any warranty arising from course of performance, course of dealing, or usage in trade.​​​​

​No license (express or implied, by estoppel or otherwise) to any intellectual property rights is granted by this document.​​
© Intel Corporation.  Intel, the Intel logo, and other Intel marks are trademarks of Intel Corporation or its subsidiaries.  Other names and brands may be claimed as the property of others​.​​