zaki work log

作業ログやら生活ログやらなんやら

Rootless modeのDockerをUbuntu 20.04へインストール (Debian 10では依存パッケージ不足で失敗)

DockerのRootless modeのセットアップを試してみた。
設定すればデフォルトではroot権限が必要なDockerを一般ユーザーでも使えるモードで動作させることができる。

現在はDockerをパッケージインストールすると、Rootless mode用のセットアップスクリプトも依存でインストールされるようになっている。

docs.docker.com

ちなみにPodmanはデフォルトでRootlessで動作する。

前提

Ubuntu 20.04であれば特に無いが、Debian Busterだと必要なパッケージが公式リポジトリに無いためインストールに失敗する。(後述)

今回の環境は以下の通り。

zaki@ubuntu-node:~$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.2 LTS"

準備

Prerequisitesの通り、uidmapをインストールする。

zaki@ubuntu-node:~$ apt search uidmap
Sorting... Done
Full Text Search... Done
uidmap/focal-updates 1:4.8.1-1ubuntu5.20.04 amd64
  programs to help use subuids

入ってないので入れる。

$ sudo apt install uidmap
zaki@ubuntu-node:~$ id -u
1000
zaki@ubuntu-node:~$ whoami
zaki
zaki@ubuntu-node:~$ grep ^$(whoami): /etc/subuid
zaki:100000:65536
zaki@ubuntu-node:~$ grep ^$(whoami): /etc/subgid
zaki:100000:65536

ディストリビューション毎の設定

Ubuntuの場合は特に準備無し。

その他はドキュメントのDistribution-specific hintの内容を確認する。

例えばDebianの場合は以下。カーネルパラメタを設定する。

Add kernel.unprivileged_userns_clone=1 to /etc/sysctl.conf (or /etc/sysctl.d) and run sudo sysctl --system.

https://docs.docker.com/engine/security/rootless/#distribution-specific-hint

要は以下のコマンドを実行すればOK.

# echo "kernel.unprivileged_userns_clone=1" > /etc/sysctl.d/rootless-docker.conf

設定したカーネルパラメタを確認。

zaki@debian-node:~$ cat /proc/sys/kernel/unprivileged_userns_clone 
0
zaki@debian-node:~$ sudo sh -c 'echo "kernel.unprivileged_userns_clone=1" > /etc/sysctl.d/rootless-docker.conf'
zaki@debian-node:~$ cat /proc/sys/kernel/unprivileged_userns_clone 
1

インストール

通常のDocker未インストールの場合

docker-ce-rootless-extrasをインストールする。 インストールするにはDockerインストール用のリポジトリを追加する必要があるため、各ディストリビューション用のインストール手順を確認する。

docs.docker.com

Ubuntuであれば以下の通り。
(ほかのディストリビューションも、docker-ceをinstallする直前まで進めれば良い)

$ sudo apt-get update
$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
$ echo \
  "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
$ sudo apt-get update

設定すればaptでインストールできるようになる。

zaki@debian-node:~$ apt search docker-ce-rootless-extras
ソート中... 完了
全文検索... 完了  
docker-ce-rootless-extras/buster 5:20.10.5~3-0~debian-buster amd64
  Rootless support for Docker.

ただし、docker-ce-rootless-extrasをインストールすると依存でdocker-ceも入るため、以下の「通常のDockerがインストール済みの場合」のサービス停止を確認する。

zaki@ubuntu-node:~$ which docker
/usr/bin/docker

通常のDockerがインストール済みの場合

Dockerは止める。

Note

If the system-wide Docker daemon is already running, consider disabling it: $ sudo systemctl disable --now docker.service

https://docs.docker.com/engine/security/rootless/#install

zaki@ubuntu-node:~$ systemctl is-active docker
active
zaki@ubuntu-node:~$ systemctl is-enabled docker
enabled

Ubuntuだとインストールしただけでenabledになってるので。

zaki@ubuntu-node:~$ sudo systemctl disable --now docker
Synchronizing state of docker.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install disable docker
Removed /etc/systemd/system/multi-user.target.wants/docker.service.
Warning: Stopping docker.service, but it can still be activated by:
  docker.socket
zaki@ubuntu-node:~$ systemctl is-active docker
inactive
zaki@ubuntu-node:~$ systemctl is-enabled docker
disabled

install

パッケージ版Dockerをインストールすると、Rootless modeのDockerインストールのためのスクリプト/usr/bin/dockerd-rootless-setuptool.sh にある。

zaki@ubuntu-node:~$ which dockerd-rootless-setuptool.sh
/usr/bin/dockerd-rootless-setuptool.sh

このスクリプトをRootlessで実行したい一般ユーザーでinstallを引数に指定して実行する。

zaki@ubuntu-node:~$ dockerd-rootless-setuptool.sh install
[INFO] Creating /home/zaki/.config/systemd/user/docker.service
[INFO] starting systemd service docker.service
+ systemctl --user start docker.service
+ sleep 3
+ systemctl --user --no-pager --full status docker.service
● docker.service - Docker Application Container Engine (Rootless)
     Loaded: loaded (/home/zaki/.config/systemd/user/docker.service; disabled; vendor preset: enabled)
     Active: active (running) since Fri 2021-03-05 00:12:24 UTC; 3s ago
       Docs: https://docs.docker.com/engine/security/rootless/
   Main PID: 75116 (rootlesskit)
     CGroup: /user.slice/user-1000.slice/user@1000.service/docker.service
             ├─75116 rootlesskit --net=slirp4netns --mtu=65520 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run --propagation=rslave /usr/bin/dockerd-rootless.sh
             ├─75129 /proc/self/exe --net=slirp4netns --mtu=65520 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run --propagation=rslave /usr/bin/dockerd-rootless.sh
             ├─75146 slirp4netns --mtu 65520 -r 3 --disable-host-loopback --enable-sandbox --enable-seccomp 75129 tap0
             ├─75153 dockerd
             └─75166 containerd --config /run/user/1000/docker/containerd/containerd.toml --log-level info

Mar 05 00:12:24 ubuntu-node dockerd-rootless.sh[75153]: time="2021-03-05T00:12:24.859622362Z" level=warning msg="Your kernel does not support CPU realtime scheduler"
Mar 05 00:12:24 ubuntu-node dockerd-rootless.sh[75153]: time="2021-03-05T00:12:24.859699390Z" level=warning msg="Your kernel does not support cgroup blkio weight"
Mar 05 00:12:24 ubuntu-node dockerd-rootless.sh[75153]: time="2021-03-05T00:12:24.859760290Z" level=warning msg="Your kernel does not support cgroup blkio weight_device"
Mar 05 00:12:24 ubuntu-node dockerd-rootless.sh[75153]: time="2021-03-05T00:12:24.859931829Z" level=info msg="Loading containers: start."
Mar 05 00:12:25 ubuntu-node dockerd-rootless.sh[75153]: time="2021-03-05T00:12:25.105917170Z" level=info msg="Default bridge (docker0) is assigned with an IP address 172.17.0.0/16. Daemon option --bip can be used to set a preferred IP address"
Mar 05 00:12:25 ubuntu-node dockerd-rootless.sh[75153]: time="2021-03-05T00:12:25.315884736Z" level=info msg="Loading containers: done."
Mar 05 00:12:25 ubuntu-node dockerd-rootless.sh[75153]: time="2021-03-05T00:12:25.324807056Z" level=warning msg="Not using native diff for overlay2, this may cause degraded performance for building images: failed to set opaque flag on middle layer: operation not permitted" storage-driver=overlay2
Mar 05 00:12:25 ubuntu-node dockerd-rootless.sh[75153]: time="2021-03-05T00:12:25.325060434Z" level=info msg="Docker daemon" commit=363e9a8 graphdriver(s)=overlay2 version=20.10.5
Mar 05 00:12:25 ubuntu-node dockerd-rootless.sh[75153]: time="2021-03-05T00:12:25.325172424Z" level=info msg="Daemon has completed initialization"
Mar 05 00:12:25 ubuntu-node dockerd-rootless.sh[75153]: time="2021-03-05T00:12:25.337548054Z" level=info msg="API listen on /run/user/1000/docker.sock"
+ DOCKER_HOST=unix:///run/user/1000/docker.sock /usr/bin/docker version
Client: Docker Engine - Community
 Version:           20.10.5
 API version:       1.41
 Go version:        go1.13.15
 Git commit:        55c4c88
 Built:             Tue Mar  2 20:18:20 2021
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.5
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       363e9a8
  Built:            Tue Mar  2 20:16:15 2021
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.4.3
  GitCommit:        269548fa27e0089a8b8278fc4fc781d7f65a939b
 runc:
  Version:          1.0.0-rc92
  GitCommit:        ff819c7e9184c13b7c2607fe6c30ae19403a7aff
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
+ systemctl --user enable docker.service
Created symlink /home/zaki/.config/systemd/user/default.target.wants/docker.service → /home/zaki/.config/systemd/user/docker.service.
[INFO] Installed docker.service successfully.
[INFO] To control docker.service, run: `systemctl --user (start|stop|restart) docker.service`
[INFO] To run docker.service on system startup, run: `sudo loginctl enable-linger zaki`

[INFO] Make sure the following environment variables are set (or add them to ~/.bashrc):

export PATH=/usr/bin:$PATH
export DOCKER_HOST=unix:///run/user/1000/docker.sock

timeつけ忘れたけど10秒もかからなかった。
実行ログの最後に、環境変数設定のメッセージが出ているので、これを設定する。
(/usr/binは設定済みなので省略)

zaki@ubuntu-node:~$ export DOCKER_HOST=unix:///run/user/1000/docker.sock

なお、この環境変数は設定するパスを見ても分かる通り、使用するユーザーID毎に異なるため、別のユーザーでも使用する場合は都度dockerd-rootless-setuptool.shを設定する。

zaki@ubuntu-node:~$ docker version
Client: Docker Engine - Community
 Version:           20.10.5
 API version:       1.41
 Go version:        go1.13.15
 Git commit:        55c4c88
 Built:             Tue Mar  2 20:18:20 2021
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.5
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       363e9a8
  Built:            Tue Mar  2 20:16:15 2021
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.4.3
  GitCommit:        269548fa27e0089a8b8278fc4fc781d7f65a939b
 runc:
  Version:          1.0.0-rc92
  GitCommit:        ff819c7e9184c13b7c2607fe6c30ae19403a7aff
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

永続的に設定するには、この環境変数~/.bashrcに追記しておく。

コンテナをデプロイ

rootでなく通常ユーザーで、とりあえずwebサーバーをローカルで8080/TCPでlistenする設定で実行。

zaki@ubuntu-node:~$ docker run -d -p 8080:80 --rm --name httpd httpd
Unable to find image 'httpd:latest' locally
latest: Pulling from library/httpd
45b42c59be33: Pull complete 
83ac8490fcc3: Pull complete 
bdb2d204d86d: Pull complete 
243acf75a504: Pull complete 
8fc1ad93a9b1: Pull complete 
Digest: sha256:3c252c919ef2445a6a41dde913a56202754821af87c049c4312bf81bdbc6df4b
Status: Downloaded newer image for httpd:latest
0bd290f5a999bf82a3b71cac0d7faf1c9b15ee88567eaa11240ebe870299f379

webアクセス確認。

zaki@ubuntu-node:~$ curl localhost:8080
<html><body><h1>It works!</h1></body></html>

listenしてるポートとプロセスを確認すると、(通常のDockerであればdocker-proxyのところが)rootlesskitがlistenしていることを確認できる。

State   Recv-Q  Send-Q   Local Address:Port    Peer Address:Port  Process                                     
LISTEN  0       4096     127.0.0.53%lo:53           0.0.0.0:*      users:(("systemd-resolve",pid=688,fd=13))  
LISTEN  0       128            0.0.0.0:22           0.0.0.0:*      users:(("sshd",pid=751,fd=3))              
LISTEN  0       4096                 *:8080               *:*      users:(("rootlesskit",pid=983,fd=9))       
LISTEN  0       128               [::]:22              [::]:*      users:(("sshd",pid=751,fd=4))    

プロセス確認すると、rootでなく実行ユーザー権限で動作していることが確認できる。

zaki@ubuntu-node:~$ ps aux | grep docker
zaki         983  0.0  0.3 633996 13360 ?        Ssl  23:23   0:00 rootlesskit --net=slirp4netns --mtu=65520 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run --propagation=rslave /usr/bin/dockerd-rootless.sh
zaki        1011  0.0  0.3 779716 12660 ?        Sl   23:23   0:00 /proc/self/exe --net=slirp4netns --mtu=65520 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run --propagation=rslave /usr/bin/dockerd-rootless.sh
zaki        1034  0.0  2.2 1020492 89740 ?       Sl   23:23   0:00 dockerd
zaki        1151  0.0  1.1 898976 47252 ?        Ssl  23:23   0:00 containerd --config /run/user/1000/docker/containerd/containerd.toml --log-level info
zaki        1582  0.0  0.1 553704  7616 ?        Sl   23:32   0:00 /usr/bin/rootlesskit-docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8080 -container-ip 172.17.0.2 -container-port 80
zaki        1588  0.0  0.1 696508  4452 ?        Sl   23:32   0:00 docker-proxy -container-ip 172.17.0.2 -container-port 80 -host-ip 127.0.0.1 -host-port 8080 -proto tcp
zaki        1607  0.0  0.1 111908  7752 ?        Sl   23:32   0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 0bd290f5a999bf82a3b71cac0d7faf1c9b15ee88567eaa11240ebe870299f379 -address /run/user/1000/docker/containerd/containerd.sock
zaki        1769  0.0  0.0   6432   672 pts/0    S+   23:32   0:00 grep --color=auto docker

コンテナの実行状態を確認すると、通常ユーザーでは一覧は見えて、rootユーザーだと何も見えないことを確認できる。

zaki@ubuntu-node:~$ docker ps
CONTAINER ID   IMAGE     COMMAND              CREATED              STATUS          PORTS                  NAMES
0bd290f5a999   httpd     "httpd-foreground"   About a minute ago   Up 59 seconds   0.0.0.0:8080->80/tcp   httpd
zaki@ubuntu-node:~$ sudo docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

inspectを使うと以下の通りユーザーのホームディレクトリ以下が設定されていることがわかる。

zaki@ubuntu-node:~$ docker inspect 0bd2
[
    {
        "Id": "0bd290f5a999bf82a3b71cac0d7faf1c9b15ee88567eaa11240ebe870299f379",

        ...

        "Image": "sha256:464fdc577ef4d4ba06050b76a95ffee72d280f7aaa4291f7f4827fca7a00ed0f",
        "ResolvConfPath": "/home/zaki/.local/share/docker/containers/0bd290f5a999bf82a3b71cac0d7faf1c9b15ee88567eaa11240ebe870299f379/resolv.conf",
        "HostnamePath": "/home/zaki/.local/share/docker/containers/0bd290f5a999bf82a3b71cac0d7faf1c9b15ee88567eaa11240ebe870299f379/hostname",
        "HostsPath": "/home/zaki/.local/share/docker/containers/0bd290f5a999bf82a3b71cac0d7faf1c9b15ee88567eaa11240ebe870299f379/hosts",
        "LogPath": "/home/zaki/.local/share/docker/containers/0bd290f5a999bf82a3b71cac0d7faf1c9b15ee88567eaa11240ebe870299f379/0bd290f5a999bf82a3b71cac0d7faf1c9b15ee88567eaa11240ebe870299f379-json.log",
        "Name": "/httpd",

    ...

なお、一般ユーザー権限で動作するので、1024未満のポートは使用できない。(従来からのOSの制限)

zaki@ubuntu-node:~$ docker run -d -p 80:80 --rm httpd
557e475710bab1688172ffc7ed26bf94439551bcb28d2fe564fc5ac6f8e29f6d
docker: Error response from daemon: driver failed programming external connectivity on endpoint nostalgic_shamir (4ce60fe6e15f3f5f3af4b85e2eb06d0928a9acb2153f48e7903ba6932ba4d699): Error starting userland proxy: error while calling PortManager.AddPort(): cannot expose privileged port 80, you can add 'net.ipv4.ip_unprivileged_port_start=80' to /etc/sysctl.conf (currently 1024), or set CAP_NET_BIND_SERVICE on rootlesskit binary, or choose a larger port number (>= 1024): listen tcp 0.0.0.0:80: bind: permission denied.

メッセージの通りカーネルパラメタを設定して許可を与えることは可能。

デーモン

ユーザーごとの以下のパスにファイルが作成されている。

zaki@ubuntu-node:~$ cat ~/.config/systemd/user/docker.service 
[Unit]
Description=Docker Application Container Engine (Rootless)
Documentation=https://docs.docker.com/engine/security/rootless/

[Service]
Environment=PATH=/usr/bin:/sbin:/usr/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
ExecStart=/usr/bin/dockerd-rootless.sh 
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always
StartLimitBurst=3
StartLimitInterval=60s
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
Delegate=yes
Type=simple
KillMode=mixed

[Install]
WantedBy=default.target

systemctl--userオプションを付けて確認可能

zaki@ubuntu-node:~$ systemctl --user status docker
● docker.service - Docker Application Container Engine (Rootless)
     Loaded: loaded (/home/zaki/.config/systemd/user/docker.service; enabled; vendor preset: enabled)
     Active: active (running) since Fri 2021-03-05 00:12:24 UTC; 40min ago
       Docs: https://docs.docker.com/engine/security/rootless/
   Main PID: 75116 (rootlesskit)
     CGroup: /user.slice/user-1000.slice/user@1000.service/docker.service
             ├─75116 rootlesskit --net=slirp4netns --mtu=65520 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/r>
             ├─75129 /proc/self/exe --net=slirp4netns --mtu=65520 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up>
             ├─75146 slirp4netns --mtu 65520 -r 3 --disable-host-loopback --enable-sandbox --enable-seccomp 75129 tap0
             ├─75153 dockerd
    :
    :
zaki@ubuntu-node:~$ systemctl --user is-active docker
active
zaki@ubuntu-node:~$ systemctl --user is-enabled docker
enabled

この通り、OSをリブートしても有効。


Debian 10 (Buster)だと起動エラー

zaki@debian-node:~$ dockerd-rootless-setuptool.sh install
[INFO] Creating /home/zaki/.config/systemd/user/docker.service
[INFO] starting systemd service docker.service
+ systemctl --user start docker.service
+ sleep 3
+ systemctl --user --no-pager --full status docker.service
● docker.service - Docker Application Container Engine (Rootless)
   Loaded: loaded (/home/zaki/.config/systemd/user/docker.service; disabled; vendor preset: enabled)
   Active: activating (auto-restart) (Result: exit-code) since Sun 2021-03-07 23:51:41 JST; 973ms ago
     Docs: https://docs.docker.com/engine/security/rootless/
  Process: 6490 ExecStart=/usr/bin/dockerd-rootless.sh (code=exited, status=1/FAILURE)
 Main PID: 6490 (code=exited, status=1/FAILURE)
+ set +x
[ERROR] Failed to start docker.service. Run `journalctl -n 20 --no-pager --user --unit docker.service` to show the error log.
[ERROR] Before retrying installation, you might need to uninstall the current setup: `/usr/bin/dockerd-rootless-setuptool.sh uninstall -f ; /usr/bin/rootlesskit rm -rf /home/zaki/.local/share/docker`
No journal files were opened due to insufficient permissions.
zaki@debian-node:~$

謎エラーがでた。

journalctl -n 20 --no-pager --user --unit docker.serviceを実行してもエラーが何も記録されていない。

zaki@debian-node:~$ journalctl -n 20 --no-pager --user --unit docker.service
Hint: You are currently not seeing messages from the system.
      Users in the 'systemd-journal' group can see all messages. Pass -q to
      turn off this notice.
No journal files were opened due to insufficient permissions.
zaki@debian-node:~$

情報が何も無さすぎるので、とりあえずログの方を何とかしようと検索してみると、以下の情報がヒット。

stackoverflow.com

/etc/systemd/journald.confで以下の変更を行ってsystemd-journaldをリスタート。

@@ -13,6 +13,7 @@
 
 [Journal]
 #Storage=auto
+Storage=persistent
 #Compress=yes
 #Seal=yes
 #SplitMode=uid

これで再度dockerd-rootless-setuptool.sh installすると、以下のようにエラーが記録された。

zaki@debian-node:~$ journalctl -n 20 --no-pager --user --unit docker.service
-- Logs begin at Mon 2021-03-08 00:42:16 JST, end at Mon 2021-03-08 00:42:27 JST. --
 3月 08 00:42:25 debian-node dockerd-rootless.sh[1061]: + : builtin
 3月 08 00:42:25 debian-node dockerd-rootless.sh[1061]: + : auto
 3月 08 00:42:25 debian-node dockerd-rootless.sh[1061]: + : auto
 3月 08 00:42:25 debian-node dockerd-rootless.sh[1061]: + net=
 3月 08 00:42:25 debian-node dockerd-rootless.sh[1061]: + mtu=
 3月 08 00:42:25 debian-node dockerd-rootless.sh[1061]: + [ -z ]
 3月 08 00:42:25 debian-node dockerd-rootless.sh[1061]: + which slirp4netns
 3月 08 00:42:25 debian-node dockerd-rootless.sh[1061]: + [ -z ]
 3月 08 00:42:25 debian-node dockerd-rootless.sh[1061]: + which vpnkit
 3月 08 00:42:25 debian-node dockerd-rootless.sh[1061]: + echo Either slirp4netns (>= v0.4.0) or vpnkit needs to be installed
 3月 08 00:42:25 debian-node dockerd-rootless.sh[1061]: Either slirp4netns (>= v0.4.0) or vpnkit needs to be installed
 3月 08 00:42:25 debian-node dockerd-rootless.sh[1061]: + exit 1
 3月 08 00:42:25 debian-node systemd[549]: docker.service: Main process exited, code=exited, status=1/FAILURE
 3月 08 00:42:25 debian-node systemd[549]: docker.service: Failed with result 'exit-code'.
 3月 08 00:42:27 debian-node systemd[549]: docker.service: Service RestartSec=2s expired, scheduling restart.
 3月 08 00:42:27 debian-node systemd[549]: docker.service: Scheduled restart job, restart counter is at 3.
 3月 08 00:42:27 debian-node systemd[549]: Stopped Docker Application Container Engine (Rootless).
 3月 08 00:42:27 debian-node systemd[549]: docker.service: Start request repeated too quickly.
 3月 08 00:42:27 debian-node systemd[549]: docker.service: Failed with result 'exit-code'.
 3月 08 00:42:27 debian-node systemd[549]: Failed to start Docker Application Container Engine (Rootless).

slirp4netnsがインストールされてないのでインストールする。

packages.debian.org

と思ったのだけど、現在のstableであるDebian Busterだとslirp4netnsのバージョンは0.2.3のため、要件が合わない。(ログを見る限り0.4.0以上が必要)
なので、替わりにvpnkitを用意すれば良さそう。
ただしパッケージはなさそうなので自前で用意する必要があるかも。(未確認)

github.com

なお、sidや時期stableバージョンのBullseyeであれば1.0.1なので使用可能と思われる。(未確認)

packages.debian.org

このパッケージはCentOS 7であればバージョン0.4.3をインストール可能なので、CentOS であれば通常のパッケージインストールで使用可能だと思われる。(未確認)

[zaki@cloud-dev ~]$ yum info slirp4netns
読み込んだプラグイン:fastestmirror
Loading mirror speeds from cached hostfile
 * base: ty1.mirror.newmediaexpress.com
 * epel: ftp.jaist.ac.jp
 * extras: ty1.mirror.newmediaexpress.com
 * updates: ty1.mirror.newmediaexpress.com
インストール済みパッケージ
名前                : slirp4netns
アーキテクチャー    : x86_64
バージョン          : 0.4.3
リリース            : 4.el7_8
容量                : 169 k
リポジトリー        : installed
提供元リポジトリー  : extras
要約                : slirp for network namespaces
URL                 : https://github.com/rootless-containers/slirp4netns
ライセンス          : GPLv2
説明                : slirp for network namespaces, without copying buffers across the namespaces.

情報源

[Linux] ターミナルでコマンド履歴の検索とsttyのstop設定

bashzshはシェル上でCtrl-r押下してからコマンド名やオプションの文字列を入力すると、コマンド履歴からインクリメンタルサーチできる。

(reverse-i-search)`':

Ctrl-rを押下すると↑のようにプロンプトが変化するので、例えばここにkubectlと入力すれば、直近で実行したkbuectlコマンドを検索できる。(2つ目以前の候補検索は都度Ctrl-rを押下)

ただ、行き過ぎた場合はCtrl-sで戻れる設定になっているが、デフォルトではCtrl-sは端末のstopに割り当てられているため、コマンド検索でなくターミナルの入出力がロックされる。(解除はCtrl-q)
Ctrl-sは例えばリアルタイムで大量に流れ続けるログをtail -fで見ているときに一時停止したい場合などに使用する
※※ うっかりどこか変なキーコンビネーションを押してターミナルがまったく動かなくなった場合は(ネットワークが中途半端に切断された場合を除いて)これが原因のことが大半なのでとりあえずCtrl-qすれば動くかも

stty 設定

確認

この「Ctrl-sは端末のstop」という設定は stty -aで確認できる。

$ stty -a
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = M-^?; eol2 = M-^?; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo -echoe -echok -echonl -noflsh -xcase -tostop -echoprt -echoctl -echoke

使用頻度の高いCtrl-cCtrl-zなどもここで確認できる。

設定変更

設定を変更する場合、例えばstopCtrl-xにバインドするには以下を実行。
(^xは、Ctrl-vの後に制御コード入力でなく、ハット・スモールエックスの2文字でOK)

$ stty stop ^x

この後に確認すると、以下の通り、設定が変更される。

$ stty -a

... start = ^Q; stop = ^X; ...

設定無効化

「いや、stopなんか不要だ!一瞬でも目に入ればわかる!」って場合は、オフにもできる。

$ stty stop undef

この設定を行うと、内容は以下の通り。

$ stty -a
speed 38400 baud; rows 30; columns 246; line = 0;
... start = ^Q; stop = <undef>; ...

:

詳しくはman stty参照。

永続的に設定したい場合は、$HOME/.bashrcにこれらのコマンドを書いておけばOK

まとめ

コマンド履歴を新->旧方向へインクリメンタルサーチするにはCtrl-rをタイプする。
プロンプトは以下。

(reverse-i-search)`':

コマンド履歴を旧->新方向へインクリメンタルサーチするにはCtrl-sをタイプする。
ただしデフォルトで端末制御がCtrl-sを喰ってしまうので使いたい場合はそちらは無効化するか別キーを割り当てる。
プロンプトは以下。

(i-search)`':

いずれもEmacsキーバインドと同じで、Ctrl-risearch-backwardCtrl-sisearch-forwardと、逆サーチ、順サーチがそれぞれ割り当てられている。

履歴のインクリメンタルサーチとCtrl-sによるstop / [unix][shell] | 戯術者の日記

ちなみにfzfを使えば、Ctrl-rで検索モードになったあと↑や↓で検索結果を選べるのでCtrl-s無くてもよかったりするけどね。

[Docker / Docker Compose] コンテナのIPアドレスを固定する方法

以前「コンテナ若葉マーク」で「コンテナ環境ではIPアドレスじゃなくてコンテナ名を使って通信しろ!(IPアドレスは意識するな!)」みたいなことを話したりしたことあったんですが、何らかの理由でコンテナ名(ホスト名)でなくIPアドレスを使って(イコールIPアドレスを固定して)コンテナを使いたい場合について。

コンテナ単体で固定IPアドレスを使いたいユースケースはさすがに無いと思うので、複数のコンテナをデプロイしてお互いのコンテナ間通信の際にIPアドレスを固定したい、という場合について、Docker単体(docker実行)の場合と、Docker Compose使用時それぞれについて説明します。

まずDocker Networkを作成してから、そのネットワーク上にコンテナをデプロイ、という構成は同じです。

Docker単体

ここではコンテナを10.254.253.0/24のアドレスのネットワーク上にデプロイしてみます。

Network作成

Dockerでコンテナ間通信をしたい場合は、まずDocker Networkを作成します。

その際、IPアドレス固定にしたい場合はDocker Networkのサブネットアドレスを指定することでアドレス固定することができます。

$ docker network create --subnet 10.254.253.0/24 fixed_container_network
e79041fae649459e9172e078ef81bb33b1adf33c5ee0fe2270de5dcb47cfd57e

↑で作成したDocker Networkは↓で確認できます。

$ docker network ls | grep fixed_container_network
e79041fae649   fixed_container_network      bridge    local

ホストOSのip aでネットワークインタフェースを確認すると以下の通り。

$ ip a
:
:
643: br-e79041fae649: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:58:bf:b6:14 brd ff:ff:ff:ff:ff:ff
    inet 10.254.253.1/24 brd 10.254.253.255 scope global br-e79041fae649
       valid_lft forever preferred_lft forever

fixed_container_network という名前のDocker Networkが作成されました。

ネットワークのみ指定のコンテナ

IPアドレスは固定せずにネットワークのみ指定する場合は以下の通り。

$ docker run --network fixed_container_network -d --rm --name debian-container debian:latest tail -f /dev/null 
416bea2cddb4d9245e5d9d25fae60861b005de4fcf73faa344ab8c57d428987d

コンテナのIPアドレスを指定しない場合、割り当てられるIPアドレスは以下の通り。

$ docker inspect debian-container --format "{{ .NetworkSettings.Networks.fixed_container_network.IPAddress }}"
10.254.253.2

指定したDocker Networkのサブネットのアドレスのうち、空いているものが割り当てられます。

IPアドレス指定

アドレスを固定するには--ipオプションを使用します。

$ docker run --network fixed_container_network -d --rm --name debian-container-fixed-addr --ip 10.254.253.199 debian:latest tail -f /dev/null 
3ad749fbc5783ad9182439eba66fcef08c9422428b31170d30a40013011ce44b

デプロイされたコンテナのIPアドレスは以下の通り。

$ docker inspect debian-container-fixed-addr --format "{{ .NetworkSettings.Networks.fixed_container_network.IPAddress }}"
10.254.253.199

Docker Compose

Docker Composeを使ってもネットワークのサブネットアドレス固定と、コンテナのIPアドレス固定の指定ができます。

Docker Compose使用時はネットワークの定義は特に追加設定が不要な場合は定義そのものを省略できる(省略時はオートで作成される)けど、前述と同じようにサブネットアドレスを指定する場合は明示的に定義します。

ここではコンテナを10.254.249.0/24のアドレスでデプロイしてみます。

Network定義

固定サブネットアドレスでネットワークを定義するには以下の通り。

networks:
  fixed_compose_network:
    ipam:
      driver: default
      config:
        - subnet: 10.254.249.0/24

fixed_compose_networkはネットワーク名なので任意。
そのパラメタのipam以降の定義で、サブネットアドレスを指定します。

ネットワークのみ指定のコンテナ

services:
  sample_container1:
    image: debian:latest
    command: tail -f /dev/null
    networks:
      fixed_compose_network:

前述の10.254.249.0/24を設定したネットワークを指定したコンテナをデプロイするにはこの通り。

コンテナに対するIPアドレスは指定していないけど、docker-compose up -dすると、以下の通り。 (fixed_address_sampleは実行ディレクトリ名が使われている)

$ docker inspect fixed_address_sample_sample_container1_1 --format "{{ .NetworkSettings.Networks.fixed_address_sample_fixed_compose_network.IPAddress }}"
10.254.249.2

IPアドレス指定

前述の10.254.249.0/24を設定したネットワークを指定しつつ、さらにipv4_addressIPアドレス指定でコンテナをデプロイするにはこの通り。

  sample_container2:
    image: ubuntu:latest
    command: tail -f /dev/null
    networks:
      fixed_compose_network:
        ipv4_address: 10.254.249.89

この内容を追加してdocker-compose up -dすると(既存Compose Fileに追加したあとは停止→起動せずにupのみでOK)、

$ docker inspect fixed_address_sample_sample_container2_1 --format "{{ .NetworkSettings.Networks.fixed_address_sample_fixed_compose_network.IPAddress }}"
10.254.249.89

この通り、指定したIPアドレスが割り当てられています。

see: networks / Compose file version 3 reference | Docker Documentation


Compose File全体は以下の通りです。

version: '3'
services:
  sample_container1:
    image: debian:latest
    command: tail -f /dev/null
    networks:
      fixed_compose_network:

  sample_container2:
    image: ubuntu:latest
    command: tail -f /dev/null
    networks:
      fixed_compose_network:
        ipv4_address: 10.254.249.89

networks:
  fixed_compose_network:
    ipam:
      driver: default
      config:
        - subnet: 10.254.249.0/24

まとめ

アプリケーションがホスト名でなくIPアドレスを指定する必要があるなどネットワーク回りで固有の事情がある場合、IPアドレス固定を行うためのコンテナ実行についてまとめました。

環境

$ docker --version
Docker version 20.10.3, build 48d30b5
$ docker-compose version
docker-compose version 1.27.4, build unknown
docker-py version: 4.4.1
CPython version: 3.6.8
OpenSSL version: OpenSSL 1.0.2k-fips  26 Jan 2017
$ cat /etc/centos-release
CentOS Linux release 7.9.2009 (Core)

参考

docs.docker.com

このページの以下をチェック。

[NetBox] Docker Compose版NetBoxを1.0.2へアップグレード

Docker ComposeでデプロイしたNetBoxをバージョンアップする。
基本的には「動作中NetBoxを停止 -> Composeファイルを(リポジトリごと)更新 -> 新バージョンNetBoxを起動」で良いが、更新内容によっては追加手順があるのでここ最近のバージョンから最新安定版へのアップグレード時のチェック箇所について簡単にまとめた。

見るべきポイントは基本的にGitHubリポジトリのReleasesページ。

github.com

詳細が必要な場合はNetBox本体のドキュメントも参照する。

netbox.readthedocs.io

本記事はversin 0.27.0 から現バージョンの1.0.2へのアップグレード例。
それ以前空のアップグレードについてはわかる範囲で調査・検証した。

現バージョンの確認

確認するバージョンはNetBox本体とnetbox-dockerのバージョンの両方を確認。
Docker版NetBoxは、NetBox本体のバージョンと、Docker Composeとしてのバージョンの2系統あるためちょっとややこしいので注意。

NetBox本体は、webアクセスした際のページヘッダに表示される。

f:id:zaki-hmkc:20210223155117p:plain

6e5c27b39de8 (v2.10.3)

netbox-dockerのバージョンは、cloneしたリポジトリ直下のVERSIONファイルを確認する。

[zaki@manager netbox-docker]$ cat VERSION 
0.27.0

アップグレード内容の確認

GitHubのReleaseの内容を確認する。

github.com

ここで upgrad compati earlier あたりの単語検索しておく。

NetBox本体の互換性

Release Noteの各バージョンの「Compatibility」を確認。

例えばNetBox本体がv2.9.xからのアップグレードの場合は、「Version 0.26.2はNetBox v2.9.xと互換あり」の状態から、その次の「Version 0.27.0はNetBox 2.10.xと互換あり」になっているため、このバージョンをまたぐアップグレードの場合(netbox-dockerのバージョン0.26.2以前からのアップグレードの場合)は、内容をよく確認しておく。

このバージョンの場合はinitializersを使用している場合に追加手順が必要。
DBのマイグレーションとあるので、DBコンテナのみ起動してDjangomanage.pyを実行すればよさそう。(詳細未確認)

github.com

github.com

PostgreSQLのバージョン

比較的目立たない気がするけど重要な件で、netbox-dockerのVersion 0.26.0以降では使用するDBが、PostgreSQL 11からPostgreSQL 12に変更されている。

このアップグレードはどちらかの対応が必要。
(11のデータのままだと12のPostgreSQLは利用できない)

PostgreSQL 11のまま使用する場合は、docker-compose.override.ymlで設定を上書きし、postgres:11-alpineを使用するようにすれば動作する。
DBのバックアップ&リストアによる更新は後述。

アップグレード

まず現バージョンのNetBoxを停止。 (git pullするとdocker-compose.ymlが更新されてしまうので先に停止する。先にgit pullしてしまった場合は、git checkout <tagname>で戻してからdocker-compose downする)

$ docker-compose down
Stopping netbox-docker_nginx_1         ... done
Stopping netbox-docker_netbox_1        ... done
Stopping netbox-docker_netbox-worker_1 ... done
Stopping netbox-docker_postgres_1      ... done
Stopping netbox-docker_redis_1         ... done
Stopping netbox-docker_redis-cache_1   ... done
Removing netbox-docker_nginx_1         ... done
Removing netbox-docker_netbox_1        ... done
Removing netbox-docker_netbox-worker_1 ... done
Removing netbox-docker_postgres_1      ... done
Removing netbox-docker_redis_1         ... done
Removing netbox-docker_redis-cache_1   ... done
Removing network netbox-docker_default

停止したことを確認。
DBをバックアップする場合はこのタイミングでDBのコンテナのみ起動してpg_dumpする。

$ docker-compose ps
Name   Command   State   Ports
------------------------------

リポジトリを更新。

$ cat VERSION 
0.27.0
$ git pull
remote: Enumerating objects: 263, done.
remote: Counting objects: 100% (263/263), done.
remote: Compressing objects: 100% (96/96), done.
remote: Total 433 (delta 175), reused 228 (delta 162), pack-reused 170
Receiving objects: 100% (433/433), 85.81 KiB | 285.00 KiB/s, done.
Resolving deltas: 100% (260/260), completed with 50 local objects.
From https://github.com/netbox-community/netbox-docker
   aa4d630..c80fb19  release    -> origin/release
   4e8588a..009eb1f  develop    -> origin/develop
 * [new tag]         1.0.2      -> 1.0.2
 * [new tag]         1.0.0      -> 1.0.0
 * [new tag]         1.0.1      -> 1.0.1
Updating aa4d630..c80fb19
Fast-forward
 .ecrc                                                 | 23 +++++++++++++++++++++++
 .editorconfig                                         | 11 +++++++++++

 :
 :
$ cat VERSION 
1.0.2

ここでnetbox-docker version 1.0.0未満からのアップグレードの場合はNginxコンテナを使わなくなっているため、docker-compose.override.ymlに上書き設定を記述している場合は更新する。(ここが元のままだと次のdocker-compose pullが失敗する)

  version: '3.4'
  services:
-   nginx:
+   netbox:
      ports:
      - '8080:8080'

latestの中身が更新されているのでイメージをpullする。

$ docker-compose pull
Pulling postgres      ... waiting
Pulling redis-cache   ... waiting
Pulling redis         ... waiting
Pulling netbox-worker ... downloading (86.6%)
Pulling netbox        ... downloading (86.6%)

DBのバージョンを更新した場合はこのタイミングでリストアする。
準備できたらデプロイする。

$ docker-compose up -d
Creating network "netbox-docker_default" with the default driver
Creating netbox-docker_postgres_1    ... done
Creating netbox-docker_redis_1       ... done
Creating netbox-docker_redis-cache_1 ... done
Creating netbox-docker_netbox-worker_1 ... done
Creating netbox-docker_netbox_1        ... done

f:id:zaki-hmkc:20210223161309p:plain

アップグレードできた。

バックアップ・リストアによるPostgreSQL 12へのデータ移行

PostgreSQL 12へ移行する場合は、ver11のDBデータをバックアップし、ver12でリストアすると良い。

Troubleshooting / Database Operations

NetBoxを一度停止。

$ docker-compose stop
Stopping netbox-docker_nginx_1         ... done
Stopping netbox-docker_netbox_1        ... done
Stopping netbox-docker_netbox-worker_1 ... done
Stopping netbox-docker_redis-cache_1   ... done
Stopping netbox-docker_postgres_1      ... done
Stopping netbox-docker_redis_1         ... done

pg_dumpを使ったデータのバックアップのためDBのみ起動。

$ docker-compose up -d postgres
Starting netbox-docker_postgres_1 ... done
$ docker-compose ps
            Name                           Command               State     Ports  
----------------------------------------------------------------------------------
netbox-docker_netbox-worker_1   python3 /opt/netbox/netbox ...   Exit 0           
netbox-docker_netbox_1          /opt/netbox/docker-entrypo ...   Exit 0           
netbox-docker_nginx_1           nginx -c /etc/netbox-nginx ...   Exit 0           
netbox-docker_postgres_1        docker-entrypoint.sh postgres    Up       5432/tcp
netbox-docker_redis-cache_1     docker-entrypoint.sh sh -c ...   Exit 0           
netbox-docker_redis_1           docker-entrypoint.sh sh -c ...   Exit 0   

DBにアクセスできることを確認。

$ source env/postgres.env 
$ docker-compose exec postgres sh -c 'psql -U $POSTGRES_USER $POSTGRES_DB'
psql (11.11)
Type "help" for help.

netbox=# 

PostgreSQLのデータバックアップ。

$ docker-compose exec -T postgres sh -c 'pg_dump -cU $POSTGRES_USER $POSTGRES_DB' | gzip > db_dump.sql.gz
ls -l db_dump.sql.gz
-rw-rw-r--. 1 zaki zaki 48388  2月 23 15:19 db_dump.sql.gz

データのバックアップが終わったらPostgreSQLを停止、アップグレードしたPostgreSQL 12でPostgreSQL 11のデータを読み込むとエラーになるため、ボリュームも削除する。

$ docker-compose down
Stopping netbox-docker_postgres_1 ... done
Removing netbox-docker_nginx_1         ... done
Removing netbox-docker_netbox_1        ... done
Removing netbox-docker_netbox-worker_1 ... done
Removing netbox-docker_redis-cache_1   ... done
Removing netbox-docker_postgres_1      ... done
Removing netbox-docker_redis_1         ... done
Removing network netbox-docker_default
$ docker volume ls | grep netbox | grep postgres
local     netbox-docker_netbox-postgres-data
$ docker volume rm netbox-docker_netbox-postgres-data
netbox-docker_netbox-postgres-data

この状態でgit pull, docker-compose pullリポジトリ・イメージを更新し、まずはDBをリストアするためにPostgreSQLのみデプロイしてデータをリストアする。 (docker-compose.override.ymlnetboxの上書き設定がある場合はnetboxに修正する)

$ docker-compose up -d postgres
Creating network "netbox-docker_default" with the default driver
Creating volume "netbox-docker_netbox-postgres-data" with local driver
Creating netbox-docker_postgres_1 ... done
$ gunzip -c db_dump.sql.gz | docker-compose exec -T postgres sh -c 'psql -U $POSTGRES_USER $POSTGRES_DB' 
SET
SET
SET
SET
SET
 set_config 
------------
 
(1 row)

:
:

残りのコンテナも起動する。

$ docker-compose up -d 
netbox-docker_postgres_1 is up-to-date
Creating netbox-docker_redis-cache_1 ... done
Creating netbox-docker_redis_1       ... done
Creating netbox-docker_netbox-worker_1 ... done
Creating netbox-docker_netbox_1        ... done
$ docker-compose ps
            Name                           Command               State                       Ports                     
-----------------------------------------------------------------------------------------------------------------------
netbox-docker_netbox-worker_1   /opt/netbox/venv/bin/pytho ...   Up                                                    
netbox-docker_netbox_1          /opt/netbox/docker-entrypo ...   Up      0.0.0.0:8099->8080/tcp,0.0.0.0:49177->8080/tcp
netbox-docker_postgres_1        docker-entrypoint.sh postgres    Up      5432/tcp                                      
netbox-docker_redis-cache_1     docker-entrypoint.sh sh -c ...   Up      6379/tcp                                      
netbox-docker_redis_1           docker-entrypoint.sh sh -c ...   Up      6379/tcp   

これでPostgreSQL 12にデータを引き継げるはず。

注意点としてはドキュメントの以下記述。

  • If your database is not too big, a full backup (with PostgreSQL 11, i.e. before the upgrade) and restore (with a clean PostgreSQL 12, i.e. after the upgrade) is probably the easiest to achieve
  • Please test the procedure on a test system first!

データがデカすぎると…リソースを喰いすぎるのかな?
あとテストをよく実施するように、ということ。

※ 上記も適当なデータ入れた状態で試したレベルなので、実際にやる場合はよく検証してください。

検証のため古いバージョンでデプロイする

リポジトリバージョンをターゲットに合わせてcheckoutする。

$ git checkout 0.26.2

また、これだけだとコンテナイメージはlatestを持ってきてしまうため、環境変数VERSIONv2.9など必要に応じて設定すれば指定バージョンでデプロイできる。

services:
  netbox: &netbox
    image: netboxcommunity/netbox:${VERSION-latest}

[ssh / docker / kubectl] ローカルの標準出力をリモートの標準入力にする

sshdocker execkubectl execで、シェルを起動せずに直接コマンドを実行できるが、sshなどを実行するローカルの標準出力をリモートホストやコンテナの標準入力として処理する方法について。

SSHの場合

前提。
ローカルのhostnameはcloud-devで、リモートはrhel8となっている。

[zaki@cloud-dev ~]$ hostname
cloud-dev
[zaki@cloud-dev ~]$ ssh 192.168.0.27 hostname
rhel8

ローカルのhostnameコマンド実行結果をリモートにパイプし、リモートではそれを標準入力(-)から受けてファイルへリダイレクト。(コマンドにあまり意味はない)

[zaki@cloud-dev ~]$ hostname | ssh 192.168.0.27 sh -c 'cat - > /tmp/hostname.txt'

結果、リモートでは/tmp/hostname.txtが生成され、ローカルのhostnameの結果が保存される。
ローカルにはこのファイルは無い。

[zaki@cloud-dev ~]$ ssh 192.168.0.27 cat /tmp/hostname.txt
cloud-dev
[zaki@cloud-dev ~]$ ls /tmp/hostname.txt
ls: /tmp/hostname.txt にアクセスできません: そのようなファイルやディレクトリはありません

Dockerのコンテナの場合

前提。
debianイメージでデプロイしたコンテナIDa24d81dbfee2のコンテナが動作している。
コンテナでhostnameを実行するとコンテナIDと同じになっている。

[zaki@cloud-dev ~]$ docker ps
CONTAINER ID   IMAGE      COMMAND                  CREATED         STATUS        PORTS     NAMES
a24d81dbfee2   debian     "tail -f /dev/null"      2 seconds ago   Up 1 second             relaxed_heisenberg
[zaki@cloud-dev ~]$ docker exec -i a24d hostname
a24d81dbfee2

前述SSHの例と同様に、ローカルのhostnameの実行結果をパイプでdocer execで実行中コンテナの標準入力としてファイルへリダイレクト。
ポイントは-iを付与すること。これが無いと標準入力を引き継げずに入力がなくなり、空ファイルが生成される。
また、-iとセットで使うことの多い-tは、付与するとthe input device is not a TTYとエラーになるので使用しない。

[zaki@cloud-dev ~]$ hostname | docker exec -i a24d sh -c 'cat - > /tmp/hostname.txt'

結果、コンテナ内の/tmp/hostname.txtにローカルのコマンド実行結果が保存される。

[zaki@cloud-dev ~]$ docker exec a24d cat /tmp/hostname.txt
cloud-dev
[zaki@cloud-dev ~]$ ls /tmp/hostname.txt
ls: /tmp/hostname.txt にアクセスできません: そのようなファイルやディレクトリはありません

KubernetesのPodの場合

前提。
HTTPサーバーのPodであるsample-httpが動作している。
Pod名はsample-http-6c94f59975-w89gdでPod内コンテナのホスト名も同じ名称になっている。

[zaki@cloud-dev ~]$ kubectl get pod
NAME                            READY   STATUS    RESTARTS   AGE
sample-http-6c94f59975-w89gd    1/1     Running   0          7d12h
[zaki@cloud-dev ~]$ kubectl exec sample-http-6c94f59975-w89gd -- hostname
sample-http-6c94f59975-w89gd

SSHとDockerの例同様、ローカルのhostnameの実行結果をPodへパイプし、Pod内でファイルへリダイレクトしてみる。
ポイントはDocker同様に-iを付与すること。これが無いと標準入力を引き継げずに入力がなくなり、空ファイルが生成される。
また、やはりdocker execと同様に-tを併用すると Unable to use a TTY - input is not a terminal or the right kind of fileというエラーになるので使用しない。

[zaki@cloud-dev ~]$ hostname | kubectl exec -i sample-http-6c94f59975-w89gd -- sh -c 'cat - > /tmp/hostname.txt'
[zaki@cloud-dev ~]$ kubectl exec sample-http-6c94f59975-w89gd -- cat /tmp/hostname.txt
cloud-dev
[zaki@cloud-dev ~]$ ls /tmp/hostname.txt
ls: /tmp/hostname.txt にアクセスできません: そのようなファイルやディレクトリはありません

使いどころ

個人的によく使うパターンは、ローカルにあるtar.gzのアーカイブファイルを、「リモートに転送してリモート上で展開せず」に、「tar.gzの中身を標準出力で転送してリモートで展開する」ことで、リモート上にtar.gzのアーカイブファイルそのものの転送と展開後の削除を省略したりする。
同じように、(この文脈だと紛らわしいけど)ローカルのコンテナイメージをdocker saveの結果をファイルではなく標準出力でsshにパイプし、リモートでdocker loadすることでイメージのtarファイル作成・転送・展開を一気にできる。

コンテナの場合も、例えばDBの初期構築用SQLファイルをローカルから一気にコンテナ内のコマンドに流したりできる。

SSHは昔から(横着したいときに特に)よく使ってたんだけど、そういえばDockerやKubernetesで同じことできるんだっけ?と思って試してみたらちゃんとできた。

[Ansible / Jinja2] select / selectattr を使った配列と辞書のフィルタリング

配列や、辞書の配列の中から特定の条件の要素を抜き出す。
配列操作はselect()で要素に対してフィルター処理を行い、辞書操作はselectattr()でkey指定することでvalueにフィルター処理を行う。

ここでいうフィルター処理は、AnsibleやJinja2のフィルターのことではなく、AnsibleやJinja2のTest(演算子/operatorと言うと個人的にはわかりやすいんだけど、「演算子」という表記は特に無く、Jinja2だとisを使った被演算子の方で関数的に使用)を使って要素をフィルタリングすることを指す。

Ansibleで変数名 is HogeHoge(...)という書式で使われているHogeHoge()の部分。 これをselect()selectattr()で使って配列・辞書に対してまとめて処理可能になっている (はず)。

select()selectattr()は、どちらもJinja2テンプレートのサイトにBuiltin Filtersとして記載があるのでそちらを参照。

jinja.palletsprojects.com

この記事では、以下のバージョンを前提としている。

  • Ansible: 2.10.5
  • Python: 3.6.8
  • Jinja2: 2.11.2
  • OS: CentOS 7.9.2009

配列 (select)

指定したTestの演算結果がtrueの場合にその要素を返す。
って書くとなんか分かりづらいけど、要は条件に合致した配列要素の場合のみ、その要素を返す。

数値 (一致・大小)

比較演算子が使える。

  vars:
    integer_values:
      - 0
      - 1
      - 2
      - 3
      - 4

  - name: select integer values
    debug:
      msg:
      - '{{ integer_values | select("eq", 2) }}'  # 2と同値か  => [2]
      - '{{ integer_values | select("==", 2) }}'  # "eq"と同じ
      - '{{ integer_values | select("le", 3) }}'  # 3以下か    => [0,1,2,3]
      - '{{ integer_values | select("<=", 3) }}'  # "le"と同じ
      - '{{ integer_values | select("lt", 3) }}'  # 3未満か    => [0,1,2]
      - '{{ integer_values | select("<", 3) }}'   # "lt"と同じ

文字列 (一致・大小)

数値の場合と同様、eqまたは==で一致した場合。
(intは==、strはeqみたいに、どっちかに統一して欲しかったな、これ。。)

  vars:
    hosts:
      - web1
      - web2
      - web3
      - db1
      - db2

  tasks:
  - name: split filter sample
    debug:
      msg:
      - '{{ hosts | select("eq", "web1") }}'  # web1が取れる

文字列に対してはあまり使う頻度は無いと思うが、gt/>, ge/>=, lt/<, le/<= も使える。

正規表現

matchsearchを使った正規表現も使える。
正規表現はJinja2のTestでなく、Ansibleで用意されている。
処理内容はPythonPattern#match()Pattern#search()となっている。

Testing strings

  vars:
    hosts:
      - web1
      - web2
      - web3
      - db1
      - db2
      - web1
      - dns

  tasks:
  - name: split filter sample
    debug:
      msg:
      - '{{ hosts | select("match", "web") }}' # webにmatchするもの
      - '{{ hosts | select("match", "b") }}'   # bが含まれるもの…と思いきやre.match("b")っぽい。bで始まるもののみ
      - '{{ hosts | select("search", "b") }}'  # bがどこかに含まれるもの re.search("b")っぽい
      - '{{ hosts | select("regex", "b") }}'   # デフォルトでは"search"と同じように動作。

また、regexもあり、これはmatch_typeという名前付き引数を指定することで、Pythonのreで使用するメソッドを指定できる。

'{{ hosts | select("regex", "d.*\d+", match_type="fullmatch") }}'   # match_typeでメソッド指定

docs.python.org

型でフィルタリング

配列要素として数値や文字列・booleanなどバラバラの型の値が入っている場合に、型でフィルタリングもできる。
型名の文字列のみを引数に指定する。

    type_mix:
      - 1
      - 12
      - a
      - ab
      - curry
      - 755
      - true
      - false
      - 3.14
      - "true"
      - "false"
      - "123"
      - "3.14"
      - - subitem1
        - subitem2
      - key: 1
        value: 2

- name: select values type
    debug:
      msg:
      - '{{ type_mix | select("string") }}'   # curry, 'trye', ...
      - '{{ type_mix | select("integer") }}'  # 755
      - '{{ type_mix | select("number") }}'   # 755, true, false, 3.14
      - '{{ type_mix | select("boolean") }}'  # true, false
      - '{{ type_mix | select("float") }}'    # 3.14
      - '{{ type_mix | select("sequence") }}' # リストだけではなく、文字列・リスト・辞書が取れる
      - '{{ type_mix | select("mapping") }}'  # {key, value}

sequenceのみ思ってたのと違った。。
(iterateとして処理できるとtrueになるため、辞書だけでなく文字列もヒットする)

また、numberはboolean値もヒットするので注意。
型というより、数値として処理できる変数であれば対象という感じ?(未確認)

ファイルパス

実行ホスト上に指定されたパスが存在するかどうかでフィルタリングできる。

Testing paths

  - name: file path test
    vars:
      path_list:
        - /
        - /bin
        - /usr/bin/perl
        - /usr/bin/python3
        - /not-found-path
    debug:
      msg:
      - '{{ path_list | select("directory") }}'  # /, /bin
      - '{{ path_list | select("file") }}'       # perl, python3
      - '{{ path_list | select("link") }}'       # /bin, python3
      - '{{ path_list | select("exists") }}'     # /not-found-path 以外
      - '{{ path_list | select("mount") }}'      # /

バージョン文字列

「〇〇が未インストール、または、インストール済みでバージョン□□以下の場合」みたいなことができる。

Comparing versions

  - name: version sample
    vars:
      version_string:
        - 1.0.0
        - 1.0.99999999999999999
        - 1.1.0
        - 1.1.1
        - 1.01.0
        - 1.01.1
        - 1.9.0
        - 1.10.0
        - '1.0.1'
        - '1.1.1'
        - '1.1'
    debug:
      msg:
      - '{{ version_string | select("version", "1.1.0", "gt") }}'  # ['1.1.1', '1.01.1', '1.9.0', '1.10.0', '1.1.1']

1.1.0との比較で、1.a.0を入力するとエラーになる。

fatal: [localhost]: FAILED! => 
  msg: 'Version comparison: ''<'' not supported between instances of ''str'' and ''int'''

versonについてはPythonのドキュメントに2021.02.16時点で記載がなぜか抜けているため、詳細はソースを見るしかない?

docs.python.org

StrictVersionにすると、<数字>.<数字>のような形式のバージョン文字列で無いとエラーになる。
デフォルトのLooseVersionであればもう少しユルい書式でバージョン比較できる。

Ansibleで実装されているversoin() / version_compare()についてはこちら
また、テスト(Test Pluginでなくソフトウェアテストの方のテスト)の内容についてはこちら

PythonVersion()は別途もうちょっと調べたいところ。。

含む (in)

Pythonin演算子と同じで、別途用意したリスト内に含まれる要素かどうかを、入力としてのリストの全要素に対して検査する。

  vars:
    hosts:
      - web1
      - web2
      - web3
      - db1
      - db2
      - dns
    contain_list:
      - db1
      - web1
      - mail1

  tasks:
  - name: contain list
    debug:
      msg: '{{ hosts | select("in", contain_list) }}' # ['web1', 'db1']

inと似たものにcontainsもあるが、配列に対するselectフィルターに使用する場合、containsの引数に一致する要素をフィルタリング…しそうだけどそうはならず、配列要素を更にリストへ分解してその要素に含まれていればtrueという判定となっている。(書いててよくわからん)
まぁ一致する要素を抜き出したければeqを使えばいいので配列要素に対するcontainsはナンセンスのような気もする。(使い方の認識を誤ってる可能性もあるけど…)

下記の場合、"b"を含む文字列であればtrueになる。

  vars:
    contain_list:
      - db1
      - web1
      - mail1

  tasks:
  - name: contain list
    debug:
      msg:
      - '{{ contain_list | select("contains", "b") }}'  # ['db1', 'web1']

配列要素がiterableである必要があるため、数値の配列だと型エラーとなる。

動作する例としては以下のような感じ。(見やすいようにインラインリストにしている)

  vars:
    list_in_list:
      - [1,2,3]
      - [4,5,6]
      - [7,8,9]
  tasks:
  - name: contain list
    debug:
      msg:
      - '{{ list_in_list | select("contains", 5) }}'  # '[4,5,6]

辞書 (selectattr)

辞書の配列(辞書型の変数を要素に持つ配列)に対するselectattr()は、「辞書のkey名とTest名を指定することで、そのvalueがtrueかどうか」という動作をする。
指定のTestを行い、結果がtrueの場合に要素を返すという機能自体はselect()と同じ。

配列と若干異なる点としては「指定されたkeyが存在しない場合」に備えてdefinedを使ってガード処理を行ったり、配列の場合の動作がいまいちだったcontainsを使った「配列を要素として持つ場合に、その配列中に指定の要素があるか」などがある。

Test例は配列で一通りやってるので簡単に。。
以下のような、IPアドレスやパッケージ情報のホスト情報っぽい辞書型変数を要素に持つ配列を対象にselectattr()を使ったフィルタリングを行ってみる。

- hosts: localhost
  gather_facts: no

  vars:
    hosts:
      - name: web1
        addr: 192.168.2.10
        mask: 255.255.255.0
        pkg:
          - httpd
          - php
          - python
      - name: web2
        addr: 192.168.2.11
        mask: 255.255.255.0
        pkg:
          - httpd
          - ruby
          - python
      - addr: 192.168.2.0
        mask: 255.255.255.0
        pkg:
          - php
      - name: db1
        addr: 192.168.2.20
        mask: 255.255.255.0
        pkg:
          - mysql
          - postgres
          - php

  tasks:
  - name: selectattr filter sample
    debug:
      msg:
      - '{{ hosts | selectattr("name", "defined") }}'
      - '{{ hosts | selectattr("name", "defined") | selectattr("name", "eq", "web1") }}'
      - '{{ hosts | selectattr("name", "defined") | selectattr("name", "match", ".*1") }}'
      - '{{ hosts | selectattr("pkg", "contains", "php") }}'

キーが存在するか

'{{ hosts | selectattr("name", "defined") }}'

definedで、指定のkeyが存在する要素のみを返す。
前述hostsの例であれば、結果は以下の通り。

ok: [localhost] => 
  msg:
  - - addr: 192.168.2.10
      mask: 255.255.255.0
      name: web1
      pkg:
      - httpd
      - php
      - python
    - addr: 192.168.2.11
      mask: 255.255.255.0
      name: web2
      pkg:
      - httpd
      - ruby
      - python
    - addr: 192.168.2.20
      mask: 255.255.255.0
      name: db1
      pkg:
      - mysql
      - postgres
      - php

キーnameの値との一致

'{{ hosts | selectattr("name", "defined") | selectattr("name", "eq", "web1") }}'

前述のdefinednameが存在するもののみをフィルタリングした結果に対し、更にnameの値がweb1のものを返す。

ok: [localhost] => 
  msg:
  :
  :
  - - addr: 192.168.2.10
      mask: 255.255.255.0
      name: web1
      pkg:
      - httpd
      - php
      - python

値に対する正規表現

'{{ hosts | selectattr("name", "defined") | selectattr("name", "match", ".*1") }}'

配列の場合と同様、matchsearchを使った正規表現も可能。
結果は以下の通り。

ok: [localhost] => 
  msg:
  :
  :
  - - addr: 192.168.2.10
      mask: 255.255.255.0
      name: web1
      pkg:
      - httpd
      - php
      - python
    - addr: 192.168.2.20
      mask: 255.255.255.0
      name: db1
      pkg:
      - mysql
      - postgres
      - php

含む (contains)

'{{ hosts | selectattr("pkg", "contains", "php") }}'

指定keyの値がリスト形式の場合、その配列要素に指定した文字列や数値を含む場合の要素を返す。
例の場合、valueに配列を持つkey名pkgに対して、その配列要素にphpを含む場合のホスト情報がフィルタリングされる。

ok: [localhost] => 
  msg:
  :
  :
  - - addr: 192.168.2.10
      mask: 255.255.255.0
      name: web1
      pkg:
      - httpd
      - php
      - python
    - addr: 192.168.2.0
      mask: 255.255.255.0
      pkg:
      - php
    - addr: 192.168.2.20
      mask: 255.255.255.0
      name: db1
      pkg:
      - mysql
      - postgres
      - php

前述のselect()の例で使用したinは、使い方が悪いのか空リストになった。

  - name: selectattr filter sample
    vars:
      contain_list:
        - php
        - mysql
    debug:
      msg:
      - '{{ hosts | selectattr("pkg", "in", contain_list) }}'

↑の内容のplayで実行すると以下の通り。

ok: [localhost] => 
  msg:
  - []

(おまけ) reject() / rejectattr()

条件に合致しない要素が欲しい場合は、逆の処理となるreject()rejectattr()を使用する。

見るべきドキュメント等

select() / selectattr() フィルター本体

ソースも確認すると理解が深まる。
実は中身はどちらも(さらに言うとreject()とrejectattr()も)select_or_reject()をコールしてるのよね。

Jinja2 Tests

まずJinja2 Templateのページ。
ここの「List of Builtin Tests」のリストにあるTestsが使用できる。
一致不一致や型チェックなどの基本的なTestはここで用意されている。

jinja.palletsprojects.com

ページ右下でバージョンを選べるが、これはAnsibleで使用しているPythonパッケージのJinja2のバージョンに合わせればよい。

Ansible Tests

Jinja2 TemplateのTest以外にAnsibleで内蔵されているTestもある。
Filterと同じくコレクション等のpluginリストにはないが、このページで一通り確認することができる。

docs.ansible.com

Testsのソースはcallbackやlookupなどのほかのpluginと同様に、testというディレクトリ以下にある。

まとめ

selectattr() + map()で指定キー配下の情報のみ抽出の例はよく見かけるけど、select()selectattr()についての情報があまりなかった気がするのでまとめてみた。
どちらのFilterもAnsibleではなくJinja2の機能で、使用する主要なTest(演算子)もJinja2のドキュメントに記載があるのがポイント。
また、「select()selectattr()の引数で指定するのはFilterでなくTest」であることをおさえておけば、やりたいことを探しやすいと思う。(自分はここが曖昧だった)
※ 現状FilterとTestはCollection Indexから辿れず、AnsibleのFilterTestJinja2のドキュメントを見るしかないと思う。

select()selectattr()は、Perlだとgrep()相当でPowerShellだとWhere-Object相当かな。
今回は扱ってないmapは、Perlmap()相当でPowerShellだとForEach-Object相当のはず。

比較的単純なフィルタリングであればselect()+selectattr()で済ませて良いと思うが、抽出条件が複雑(深い位置にある値でフィルタリングして別要素の値を抜き出す、など)になってくると標準機能だけだとかえって分かりづらくなるため、json_query()なんかを使った方が良かったりするケースも出てくるので、要件によってやり方は考慮した方がよい。

(疑問) builtin 以外の Filter / Test は…

Collection IndexからFilterとTestを辿れないということは、FilterTestに載っていない(特にbuiltin以外の)Filter / Testの情報はどこから知ればいいか不明…

例えばcommunity.generalにあるjc Filterとか。

zaki-hmkc.hatenablog.com

リポジトリのプラグインのディレクトリを見るしかないのかな。

サンプルplaybook (on GitHub)

Ciscoラボ環境のCisco Modeling Labs Personal 2.1.2 をESXiに入れてみた

機会があってCisco Modeling Labsのライセンスを入手したので、手元のESXi環境で動かしてみた。
参考動画はこちら。

www.youtube.com

一旦はCML-Pの環境をセットアップして使えるところまで。
環境はESXi 6.7でRAM 64GB
作成されるCML-PのVMは、4vCPUs / RAM 8GB を要する。

購入して「My Account」を確認するとこの通り。

f:id:zaki-hmkc:20210213121353p:plain

「Download」を押下すると、製品のダウンロードページに遷移する。
本体の仮想マシンovaファイルとベアメタルデプロイ用イメージのisoファイル、あと以前から利用してる環境用のアップデート用のrpmファイルがある。(過去にはisoファイルは無かったらしい。任意の環境で構築できるようになったっぽい)

新規構築なのでovaとisoをダウンロード。
2021.02.13時点でバージョンは2.1.2。

f:id:zaki-hmkc:20210213145829p:plain

なお、ダウンロードには住所がフルで必要。
(購入のためのアカウント作成時には必須ではなかったが結局入力する必要がある)

f:id:zaki-hmkc:20210213122055p:plain

また、セットアップの最中に「refplat」のISOファイルが必要となるが、これはCML-Personal 2.1.2でなくCML-Personal 2.1.1 のリンクから入手できるので、これもダウンロードする。
(おそらく2.1.1 -> 2.1.2 の更新で変更が無かったのだろう)

f:id:zaki-hmkc:20210213145601p:plain

VM作成

ESXiなので今回はダウンロードしたovaファイルからVMを作成。
たぶんISOファイルを使えば任意の環境にセットアップできると思う。

ovaファイルからデプロイ

f:id:zaki-hmkc:20210213123249p:plain

f:id:zaki-hmkc:20210213123349p:plain

電源投入前に以下の設定を行う。

CPU設定

このホスト上で更にVMを管理するようで、CPU設定で「VTx / EPTを有効」にする設定が必要。
ESXi 6.7だとこの単語が見当たらないけど、ここ。
「ハードウェアのCPU、ソフトウェアのMMU」に設定。(もしかしたらMMUもハードウェアが正解かも。。)

f:id:zaki-hmkc:20210213140832p:plain

ストレージ

増やした方が良いらしいので16GBから32GBに変更。

ISO

ドキュメントとCML Personal 2.0の頃は「refplat」のISOダウンロードがあるが、現バージョンの2.1.2の画面からはダウンロードリンクがなかった。2.1.1にリンクがあるのでそこからダウンロードしてマウントする。

f:id:zaki-hmkc:20210213152357p:plain

以上で電源オン。

セットアップ

f:id:zaki-hmkc:20210213143608p:plain

間違ったISOファイルをマウントしていると以下の画面になるので正しいISOファイル(refplat / reference platformのISOファイル)をマウントする。

f:id:zaki-hmkc:20210213153120p:plain

アカウント作成

f:id:zaki-hmkc:20210213153150p:plain

f:id:zaki-hmkc:20210213153420p:plain

sysadminadminを作成。

ネットワーク設定

DHCP / Staticどちらか選択。
Staticの場合はIPアドレスを設定する。

f:id:zaki-hmkc:20210213154820p:plain

f:id:zaki-hmkc:20210213160315p:plain

準備が完了したらインストール処理開始。

f:id:zaki-hmkc:20210213160359p:plain

ブラウザアクセス

処理が終わったら設定したIPアドレスにブラウザでHTTPSアクセスする。(HTTPでアクセスしてもHTTPSへリダイレクトされる。)

f:id:zaki-hmkc:20210213160642p:plain

なお、9090/TCPへアクセスすれば、Cockpitにアクセスできる。
(Cockpitのログインアカウントはsysadminを使用する)

f:id:zaki-hmkc:20210214104535p:plain

また、コンソールの方も sysadmin でシェルログインできる。
(でもwheelグループが設定されてる割にはsudoの動きがよくわからん…?)

f:id:zaki-hmkc:20210213172337p:plain

ログイン

adminで設定したパスワードでログインする。

f:id:zaki-hmkc:20210213160914p:plain

画面の下部が赤いですね。

f:id:zaki-hmkc:20210213161149p:plain

ライセンスの設定が必要。

ライセンス

「Licensing」の部分のリンクか、画面右上のTools -> Licensingから。
「REGISTER...」ボタン押下。

f:id:zaki-hmkc:20210213161613p:plain

ライセンスはMy Accountのページで「2.x License」押下すると表示されるCopyボタンで取得できるので、その文字列を張り付けて「REGISTER」押下。

f:id:zaki-hmkc:20210214104422p:plain

しばらく待てばライセンスが設定・有効になる。

f:id:zaki-hmkc:20210213162630p:plain

画面下部のIssueの部分も「Status OK」に更新された。

f:id:zaki-hmkc:20210213162755p:plain

使ってみる

The Dashboard - Cisco Modeling Labs - Document - Cisco DevNet

ラボの作成

Creating a New Lab - Cisco Modeling Labs - Document - Cisco DevNet

画面右上の「ADD」から。

f:id:zaki-hmkc:20210213163919p:plain

これで一つのネットワーク環境が作成される。

f:id:zaki-hmkc:20210213163945p:plain

ノードの作成

かなり適当に以下のノードを配置してみる。

f:id:zaki-hmkc:20210213170444p:plain

ノード間を接続するには、ノードにカーソルを合わせると表示される接続マーク(?)を長押し、

f:id:zaki-hmkc:20210213170542p:plain

すると、接続したいノードまでドラッグすることで接続設定ができる。

f:id:zaki-hmkc:20210213170748p:plain

ノードへ接続すると、どのポートを使用するかのダイアログが表示されるのでポート(NIC)を選択する。

f:id:zaki-hmkc:20210213170825p:plain

f:id:zaki-hmkc:20210213170842p:plain

接続できました。
(このあとはこれを「電源オン」などすればいい模様)

参考情報

www.youtube.com

tekunabe.hatenablog.jp