Ubuntu20.04にグラボ積んでdocker-compose上でJupyterLabを動かせるようにするver2022.01


## まえがき

せっかく自宅サーバ機にgtx 1650を搭載したので、全部やり直すついでにセットアップ手順を記録します。

このサーバ構築の目標は

- dockerコンテナ上で
- docker-composeを使って
- jupyterlab環境を構築し
- かつ、搭載したグラフィックカードの機能(cuda)を使えるようにする

です。誰かの助けになれば幸いです。

所々で出てくるvimについては、お好みのテキストエディタをお使いください。

## 1. BIOS設定 & OSインストール

デスクトップPCとしても使う場合などは適宜読み替えてください。

### ハードウェア周り

あまり良く考えずに組んだ自作PCです。

-  AMD Ryzen 5 3400G with Radeon Vega Graphics
- B450 GAMING PLUS MAX (MS-7B86)
- NVIDIA Corporation TU117 \[GeForce GTX 1650\] (rev a1)
- 1TB CT1000MX500SSD1
- メモリ　たくさん

### インストールOS

- ubuntu-20.04.3-live-server-amd64.iso

公式サイトからisoデータをダウンロードしてRUFUSでUSBに書き込んで使用しています。

### BIOS設定

- 「Keyboard Error Detection」をdisabledに。
  - キーボード外して起動してもアラート上がらないようにする。
  - モノによってはデフォルトで有効(Enable)になっているので無効化する
- 出力
  - セットアップ時に外付けGPUによって画面出力が不安定になったので、以下対応しました。
  - マザーボードの優先グラフィックをIGD(内蔵GPU)に固定しておく。
  - OSインストール後にランレベルの変更。

```shell
$ systemctl get-default
graphical.target
$ sudo systemctl set-default  multi-user.target
```

### OSインストール

- IPアドレスは固定推奨。

- OpenSSHはOSインストール時に合わせて入れるか、後で ``apt install openssh-server`` 

- 自動スリープを無効化。

  - 忘れて後で変なタイミングでスリープされると面倒なので。

    ``` shell
    sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target
    ```

- apt update / apt upgrade の後、再起動

## 2. 外付けGPUのセットアップ

グラフィックカードを動かすためのドライバ等をインストールします。

```shell
$lspci | grep -i nvidia
10:00.0 VGA compatible controller: NVIDIA Corporation TU117 [GeForce GTX 1650] (rev a1)
10:00.1 Audio device: NVIDIA Corporation Device 10fa (rev a1)
```

上記結果に搭載したグラボが出てこない場合、正常に装着されていないか、グラボが壊れているか、そもそもRADEON等AMD系GPUを搭載している可能性が考えられます。

### ドライバーをインストール

※指示された手順をそのまま最後まで実行するとインストールが始まってしまうので、先に確認ください。

自分の環境の場合は[インストーラのダウンロードサイト](https://developer.nvidia.com/cuda-downloads)にて

- Linux
- x86_64
- Ubuntu
- 20.04
- deb (network)

を選択。すると下記の通りのコマンドを実行するよう言われます。

2022/01/03 Ubuntu 18.04以降は最終行で``cuda`` の代わりに``cuda-drivers`` をインストールするを実行するのが良いらしいです。[参考URL](https://medium.com/nvidiajapan/nvidia-docker-%E3%81%A3%E3%81%A6%E4%BB%8A%E3%81%A9%E3%81%86%E3%81%AA%E3%81%A3%E3%81%A6%E3%82%8B%E3%81%AE-20-09-%E7%89%88-558fae883f44)

```shell
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-ubuntu2004.pin

sudo mv cuda-ubuntu2004.pin /etc/apt/preferences.d/cuda-repository-pin-600

sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/7fa2af80.pub

sudo add-apt-repository "deb https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/ /"

sudo apt-get update
sudo apt-get install cuda
```

2.8. Handle Conflicting Installation Methods

※インストール前にバージョン衝突回避のために不要なものを削除したほうが良いらしい。

すっ飛ばしてしまった…けど大丈夫そう。ローカルでrunファイルを使ったインストール等の場合は必要かもしれないです。

> Use the following command to uninstall a Toolkit runfile installation:
>
> ```shell
> $ sudo /usr/local/cuda-X.Y/bin/cuda-uninstaller
> ```
>
> Use the following command to uninstall a Driver runfile installation:
>
> ```shell
> $ sudo /usr/bin/nvidia-uninstall
> ```
>
> Use the following commands to uninstall a RPM/Deb installation:
>
> ```shell
> $ sudo dnf remove <package_name>                      # RHEL8/CentOS8
> $ sudo yum remove <package_name>                      # RHEL7/CentOS7
> $ sudo dnf remove <package_name>                      # Fedora
> $ sudo zypper remove <package_name>                   # OpenSUSE/SLES
> $ sudo apt-get --purge remove <package_name>          # Ubuntu
> ```

インストールしたドライバのバージョンは下記で確認できます。

```shell
$ cat /proc/driver/nvidia/version
NVRM version: NVIDIA UNIX x86_64 Kernel Module  495.29.05  Thu Sep 30 16:00:29 UTC 2021
GCC version:  gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)
```

## 3. dockerをインストール

[公式サイト](https://docs.docker.com/engine/install/ubuntu/)の情報をもとにインストールしました。

1. 古いDockerバージョン等が入っている場合はアンインストールする

   ```shell
   $ sudo apt-get remove docker docker-engine docker.io containerd runc
   ```

2. dockerをインストールするのに必要な諸々(curlとか)をインストール

3. dockerを引っ張ってくるリポジトリを設定するために、gpg keyを取得

4. docker公式リポジトリを設定。stable版以外にする場合はコマンドを修正すること

5. パッケージ情報を更新してdocker関係のパッケージをインストールする。

6. hello-worldイメージを走らせて正常にdockerがインストールできているか確認する

```shell
$ sudo apt-get update
$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg \
    lsb-release -y
$ 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
$ sudo apt-get install -y docker-ce docker-ce-cli containerd.io
$ sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b8dfde127a29: Pull complete
Digest: sha256:df5f5184104426b65967e016ff2ac0bfcd44ad7899ca3bbcf8e44e4461491a9e
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/
```

- docker実行に``sudo`` を不要にする

```SHELL
# dockerグループがなければ作る
sudo groupadd docker

# 現行ユーザをdockerグループに所属させる
sudo gpasswd -a $USER docker

# dockerデーモンを再起動する (CentOS7の場合)
sudo systemctl restart docker

# exitして再ログインすると反映される。
exit
```

## 4. Docker上でCUDAが動くようにする

- nvidia-container-runtime環境をセットアップする。参考：[公式github_README.md](https://github.com/NVIDIA/nvidia-container-runtime#installation)
  - 2022/01/03 **現在はnvidia-docker2を入れれば十分のようですのでこの手順はスキップ可です。** [参考URL](https://medium.com/nvidiajapan/nvidia-docker-%E3%81%A3%E3%81%A6%E4%BB%8A%E3%81%A9%E3%81%86%E3%81%AA%E3%81%A3%E3%81%A6%E3%82%8B%E3%81%AE-20-09-%E7%89%88-558fae883f44)
  - ディストリビューションに合ったリポジトリを設定する
  - nvidia-container-runtimeパッケージをインストールする
  - UbuntuはDebian-based distributionsに該当するので以下の通り。


```shell
curl -s -L https://nvidia.github.io/nvidia-container-runtime/gpgkey | \
  sudo apt-key add -
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-container-runtime/$distribution/nvidia-container-runtime.list | \
  sudo tee /etc/apt/sources.list.d/nvidia-container-runtime.list
sudo apt-get update
sudo apt-get install nvidia-container-runtime
```

- 正常にインストールできたら再起動する

```shell
sudo reboot
```

- nvidia-docker2入れたほうが良さげなので入れる。[公式サイト](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html#docker)参考にインストール

  - 2022/01/03 **現在はnvidia-docker2を入れれば十分のようです。** [参考URL](https://medium.com/nvidiajapan/nvidia-docker-%E3%81%A3%E3%81%A6%E4%BB%8A%E3%81%A9%E3%81%86%E3%81%AA%E3%81%A3%E3%81%A6%E3%82%8B%E3%81%AE-20-09-%E7%89%88-558fae883f44)
  - リポジトリ追加
  - インストール
  - dockerコマンドで動くか確認
    - cudaイメージにlatestタグはない(なくなった)ので、ちゃんとイメージタグを指定してやる必要があることに注意
    - nvidia-smiで出力される ``CUDA Version`` がコンテナに記載されているcudaバージョンより新しいものになっていますが、動作自体は問題無いです。
  
  
  ```shell
  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
  sudo apt-get update
  sudo apt-get install -y nvidia-docker2
  sudo systemctl restart docker
  docker run --rm --gpus all nvidia/cuda:11.4.2-devel-ubuntu20.04 nvidia-smi
  Mon Jan  3 15:16:37 2022
  +-----------------------------------------------------------------------------+
  | NVIDIA-SMI 495.29.05    Driver Version: 495.29.05    CUDA Version: 11.5     |
  |-------------------------------+----------------------+----------------------+
  | GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
  | Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
  |                               |                      |               MIG M. |
  |===============================+======================+======================|
  |   0  NVIDIA GeForce ...  On   | 00000000:10:00.0 Off |                  N/A |
  | 40%   24C    P8     4W /  75W |      1MiB /  3911MiB |      0%      Default |
  |                               |                      |                  N/A |
  +-------------------------------+----------------------+----------------------+
  
  +-----------------------------------------------------------------------------+
  | Processes:                                                                  |
  |  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
  |        ID   ID                                                   Usage      |
  |=============================================================================|
  |  No running processes found                                                 |
  +-----------------------------------------------------------------------------+
  ```
  
  - /etc/docker/daemon.jsonが作成され、ランタイムの情報が記載されていることを確認
  - [他の人の](https://qiita.com/sht47/items/0706f19c67eb30e0954f#3-nvidia-docker2%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%A8etcdockerdaemonjson%E3%81%AE%E8%A8%AD%E5%AE%9A)との違いは以下の2点
    - デフォルトランタイムの指定はなくても行けました。
    - 私の環境では自動生成した結果はフルパスになってないですが、動きました。
  
  ```json
  $ cat /etc/docker/daemon.json
  {
      "runtimes": {
          "nvidia": {
              "path": "nvidia-container-runtime",
              "runtimeArgs": []
          }
      }
  }
  ```

## 5. docker-composeをインストールする。


- [参考：公式ドキュメント](https://matsuand.github.io/docs.docker.jp.onthefly/compose/install/)
  - Compose V2もあるのですがV1の方を使います。この章の内容は問題なく動くことを確認済ですので、多分どちらでも大丈夫だと思います。
  
  - インストール後、実行バイナリに対して実行権限を与える
  - 再起動して、バージョンを確認する


```shell
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
$ docker-compose --version
docker-compose version 1.29.2, build 5becea4c
```

### Docker compose上でCUDAを動かせるか確認

```yaml
services:
  test:
    image: nvidia/cuda:11.4.2-devel-ubuntu20.04
    container_name: CUDA_T
    tty: true
    deploy:
      resources:
        reservations:
          devices:
          - driver: nvidia
            count: 1
            capabilities: [gpu]
```

上記の通りdocker-compose.ymlファイルを作成して実行します。

```shell
$ mkdir test-1
$ cd test-1
$ vi docker-compose.yml
# 前述した内容をコピペ。
# 保存してvimを閉じる
:wq
$ docker-compose up -d
Creating network "test-1_default" with the default driver
Creating CUDA_T ... done
$  docker-compose exec test bash
root@0328014db009:/# nvidia-smi
Tue Jan  4 13:47:17 2022
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 495.29.05    Driver Version: 495.29.05    CUDA Version: 11.5     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  NVIDIA GeForce ...  On   | 00000000:10:00.0 Off |                  N/A |
| 40%   23C    P8     4W /  75W |      1MiB /  3911MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+
root@0328014db009:/# nvcc -V
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2021 NVIDIA Corporation
Built on Sun_Aug_15_21:14:11_PDT_2021
Cuda compilation tools, release 11.4, V11.4.120
Build cuda_11.4.r11.4/compiler.30300941_0
```

最後の ``nvcc -V`` コマンド出力結果から、コンテナ上で動いているCUDAのバージョンは11.4で、``nvidia-smi`` で出力されるCUDA Versionとは異なるものとなっていることに注意ください。

ここからは、pytorchがこのコンテナの中で動かせるか確かめます。バージョンが古いイメージを使ってる場合は気をつけてください。依存モジュールであるpillowのインストールでコケました。

```shell
root@0328014db009:/# apt-get update
***
root@0328014db009:/# apt-get install -y python3 python3-pip
***
root@0328014db009:/# pip3 install torch torchvision
***
Installing collected packages: typing-extensions, torch, pillow, numpy, torchvision
Successfully installed numpy-1.22.0 pillow-9.0.0 torch-1.10.1 torchvision-0.11.2 typing-extensions-4.0.1
root@a74688947ac5:/# python3
```

```python
Python 3.8.10 (default, Nov 26 2021, 20:14:08)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import torch
>>> print(torch.cuda.is_available())
True
>>> exit()
```

最後の出力から、pytorch上でCUDAを使用して動かすことができていることがわかります。

## 6. JupyterLabのDockerイメージを作る

JupyterlabのDockerイメージは公式からビルドに必要な一式が[Githubにて公開](https://github.com/jupyter/docker-stacks)されています。

色々なやり方があるのですが、上記リポジトリがきちんとメンテされているのでこれをcloneして大本のイメージファイルだけ書き換えて使っていこうと思います。以前は無理やり一つにまとめる方法を採用していたのですが、自力でメンテが大変なので…

JupyterLabのDockerイメージは複数のDockerイメージがあり、階層構造を成しています。[公式サイトより](https://jupyter-docker-stacks.readthedocs.io/en/latest/using/selecting.html#image-relationships)

大本になる ``base-notebook`` がもとにしている(FROMで指定している)Dockerイメージを ``nvidia/cuda`` のものに書き換えます。

### リポジトリのクローン

```shell
git clone https://github.com/jupyter/docker-stacks.git
cd docker-stacks/base-notebook
```

### Dockerfileの修正

ここで `vi Dockerfile` と入力して `base-notebook` のDockerfileを編集します。

```dockerfile
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.

# Ubuntu 20.04 (focal)
# https://hub.docker.com/_/ubuntu/?tab=tags&name=focal
ARG ROOT_CONTAINER=ubuntu:focal

FROM $ROOT_CONTAINER
# 以下略
```

 `$ROOT_CONTAINER` の指定をnvidiaが提供しているDockerイメージに変更するだけでbase-notebookのDockerfileは対応が完了します。`nvidia/cuda:11.3.1-cudnn8-runtime-ubuntu20.04` を採用しました。私はpytorchの使用を目的としていたので、本記事作成時点(2022/01/05)でpytorchの最新バージョンを確認したうえでこれを選択しました。

Tensorflowを使いたい場合もtensorflow-notebookまでDockerfileを書き換えるイメージかなと思います。cudaのバージョンなどに注意ください。

ちなみにpythonのバージョンを指定したい場合は、base-notebookのDockerfileの77行目を修正ください。

また、後続イメージのFROMに指定するため、イメージの名前は下記の通り `xxx/base-notebook` の書式にすると修正が最低限で済みます。現時点では。

```shell
# 勉強も兼ねてvimコマンド置換。sedでも直接修正でも
:6s;ubuntu:focal;nvidia/cuda:11.3.1-cudnn8-runtime-ubuntu20.04;g
# 保存してvimを閉じる
:wq
$ docker build -t haruki/base-notebook .
***
Successfully tagged haruki/base-notebook:latest
$ cd ../minimal-notebook
$ vi Dockerfile
# 3行目'jupyter'を修正
:3s/jupyter/haruki/g
:wq
$ docker build -t haruki/minimal-notebook .
***
Successfully tagged haruki/minimal-notebook:latest
$ cd ../scipy-notebook
$ vi Dockerfile
:3s/jupyter/haruki/g
:wq
$ docker build -t haruki/scipy-notebook .
***
Successfully tagged haruki/scipy-notebook:latest
```

tensorflow-notebookフォルダを複製してpytorch-notebookフォルダを作り、Dockerfileを修正 & ビルドしました。

```shell
$ cd ../
$ cp tensorflow-notebook/ pytorch-notebook -r
$ cd pytorch-notebook/
$ vi Dockerfile
:3s/jupyter/haruki/g
(色々修正)
:wq
$ docker build -t haruki/pytorch-notebook .
***
Successfully tagged haruki/pytorch-notebook:latest
```

pytorch-notebook/Dockerfileの内容は下記の通りです。pytorch関係のコマンドは[公式サイトの情報](https://pytorch.org/get-started/locally/) ほぼそのままです。

以下の通り指定しました。

- Stable(1.10.1)
- Linux
- Pip
- Python
- CUDA 11.3

```dockerfile
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
ARG OWNER=haruki
ARG BASE_CONTAINER=$OWNER/scipy-notebook
FROM $BASE_CONTAINER

LABEL maintainer="Jupyter Project <jupyter@googlegroups.com>"

# Fix DL4006
SHELL ["/bin/bash", "-o", "pipefail", "-c"]

# Install Pytorch etc
RUN mamba install --quiet --yes \
    conda-forge::cudatoolkit=11.3*=*_9 && \
    mamba clean --all -f -y && \
    pip3 install --no-cache-dir \
         torch==1.10.1+cu113 \
         torchvision==0.11.2+cu113 \
         torchaudio==0.10.1+cu113 \
         -f https://download.pytorch.org/whl/cu113/torch_stable.html && \
    fix-permissions "${CONDA_DIR}" && \
    fix-permissions "/home/${NB_USER}"
```

#### Pytorchのインストールについて

conda環境の中で、あえてpipでpytorchをインストールしたのには理由があります。

pytorchの依存モジュールであるblas(厳密にはnumpyが依存？)はいくつか種類があります。使用しているjupyter/docker-stacksではopenblasを指定していますが、condaでインストールするとmklを見に行こうとするためエラーとなってしまうようです。一方でpipを使ってインストールするとopenblasを見に行くようです。

condaでpytorchをインストールする際にopenblasを参照させる方法が出てこなかったのでpipを使ったインストールで対応しました。

- 参考サイト
  - [BLASとは](http://azalea.s35.xrea.com/blas/whatIsBlas.html#PAGETOP)
  - [Numpyに使われるBLASによって計算速度が変わるらしい](https://insilico-notebook.com/python-blas-performance/)
  - https://github.com/pytorch/pytorch/issues/37746#issuecomment-624233676
    - 古いバージョンのpytorchの話ですが参考までに。

## 7. docker-composeで動かす

必要なイメージは用意できたので、後は公式サイトの通りdockerコマンドでもよし、docker-compose もよし。

フォルダを作成して、docker-compose.ymlとボリュームマウント用のフォルダを作成します。

```shell
$ mkdir JupyterLab
$ cd JupyterLab/
$ vi docker-compose.yml
(コピペ)
:wq
$ mkdir notebook/
```

docker-compose.ymlの内容は以下の通りです。この内容の場合、ymlファイルと同じ階層に `notebook` フォルダが必要です。

フォルダがない場合は自動生成されますが、root権限で作成され PermissionDeniedが出てしまいます。事前にフォルダ作っておけば回避可能です。

```yaml
version: '3'
services:
  jupyter:
    image: haruki/pytorch-notebook
    environment:
      JUPYTER_ENABLE_LAB: "yes"
      NVIDIA_VISIBLE_DEVICES: "all"
      NVIDIA_DRIVER_CAPABILITIES: "all"
    tty: true
    ports:
     - "80:8888"
    volumes:
      - ./notebook:/home/jovyan
    deploy:
      resources:
        reservations:
          devices:
            - capabilities:
              - gpu
```

```shell
$ docker-compose up -d
$ docker-compose logs
***
jupyter_1  |     To access the server, open this file in a browser:
jupyter_1  |         file:///home/jovyan/.local/share/jupyter/runtime/jpserver-7-open.html
jupyter_1  |     Or copy and paste one of these URLs:
jupyter_1  |         http://c93a8e013e3b:8888/lab?token=****
jupyter_1  |      or http://127.0.0.1:8888/lab?token=****
```

上記のようになっていれば成功です。tokenの部分をコピーしておきます。

#### ウェブブラウザからアクセスする

最後にウェブブラウザからJupyterlabにアクセスできるか確認します。

先程の` docker-compose.yml` の内容であれば、URLとして `http://[サーバのIPアドレス]/lab` を指定すれば開けるはずです。tokenを聞かれるので、先程コピーしたtokenを貼り付けます。初回のみ聞かれます。

無事JupyterLab環境が開ければ環境構築が一旦完了です。

## 8. 公開鍵認証を設定する

SSHの公開鍵認証の設定をしていきます。Visual Studio Codeでremote containerなどを使って開発する場合、必要だったはず。ログインの手間も減り、セキュリティの向上も見込めるのでやっておいたほうが良いです。

以下はパスワード認証でのSSHアクセスができている前提で話を進めます。

なお、SSHクライアントはTeraTermの内容で記載しています。PowerShellでもTerminalでもPuTTYでもやれることは同じはずです。

### 公開鍵をサーバにわたす

- クライアント側で鍵を生成する。
  - Tera termなら設定タブから鍵生成可能
  - ed25519を使用。RSA(2048bit)でもよさげ。
- 公開鍵をSCP(or sftp)でサーバに転送
  - TeraTermならSSHでアクセスした状態でターミナル画面にファイルをドラッグアンドドロップでSCP転送ができます。
- クライアントの公開鍵をサーバに登録
  - 権限の設定が間違っていると正常に機能しないのでchmodは必ず実施する

```shell
$ mkdir ~/.ssh
$ sudo chmod 700 ~/.ssh
$ cd ~/.ssh
$ cat id_ed25519.pub>>authorized_keys
$ sudo chmod 600 authorized_keys
```

### サーバのコンフィグを修正
#### 2022/1/8追記

本日apt upgradeした際にsshdについて「コンフィグ書き換えてるけど、アップデートするからデフォルトで上書きして良い？」という趣旨の出力がありました。確かに`/etc/ssh/sshd_config` を書き換えていたのですが、 `/etc/ssh/sshd_config.d/*.conf` のような新規ファイルを作成してそこに書くのが正しいお作法のようです。

[Ubuntu 20.04 LTSのリリースノート](https://wiki.ubuntu.com/FocalFossa/ReleaseNotes/Ja) を調べたところ2020/2/14の OpenSSH 8.2 のアップデートの内容のようです。なんでこのタイミングで出たのかわからなかったのですが、見落としですね...

> DebianにおけるOpenSSH 8.2のアップストリーム開発で、設定ファイルに['Includes'キーワードのサポートが追加](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=631189)]されました。 これは、glob(3)パターン経由で追加の設定ファイルを含めることができます。 デフォルトでは**システムのsshd構成(/etc/ssh/sshd_config)に[/etc/ssh/sshd_config.d/*.conf配下のファイルが含まれる](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=952427)ようになりました。**設定ファイルのキーワードごとに、最初に取得した値が利用されます。これはさまざまな[クラウドイメージ](https://wiki.ubuntu.com/FocalFossa/ReleaseNotes/Ja#A.2BMK8w6TCmMMkwpDDhMPwwuA_.2BJgE-)で使用され、パッケージのアップグレード時にdebconfプロンプトを回避しながら、クラウド固有のチューニングを適用できます。

~~sshd_configを修正します。既存設定のコメントアウト以外はややこしくなるので末尾に追記しています。~~ 

`/etc/ssh/sshd_config.d/` 配下にlocal.confというファイルを作成して、そこに以下の内容を追記しました。

- 接続をSSH version2 に限定する
- ポート番号を変更(50000番以降を使用)
- 公開鍵認証の有効化
- パスワード認証無効化
- チャレンジ/レスポンス認証を無効
  - デフォルトで設定済
- 空パスワードを無効
- rootユーザのログインを禁止
- ログインに関するログ(成功、失敗)が出力されるようにする

```shell
$ sudo vi /etc/ssh/sshd_config.d/local.conf
###以下をコピペ
Protocol 2
port 50022
PubkeyAuthentication yes
PasswordAuthentication no
ChallengeResponseAuthentication no
PermitEmptyPasswords no
PermitRootLogin no
SyslogFacility AUTHPRIV
LogLevel VERBOSE
###コピペここまで
# 保存してvimを閉じる
:wq
```

sshdに反映される設定内容は`sudo sshd -T` で一覧取得できます。

> sshdの効果的な設定は、'sudo sshd -T' を実行することで検証できます。 これにより、複数の設定ファイルが読み取られて検証し、終了前に有効な設定が出力されます。

sshdサービスを再起動して設定は完了です。

```shell
$ sudo sshd -T
# ***長いので略
$ systemctl restart sshd
```


### 接続確認

公開鍵認証を使ったログインができるか確かめる。クライアント側の設定は割愛します。

- **すでに接続しているセッションは維持しつつ**、新しいセッションでアクセスできることを確認する
  - Tera termなら「ファイル」→「新しい接続」
  - ポート番号の変更などは見落としがちなので注意です。
  - 私はssh2login.ttlを修正してワンクリックでログインする方針にしています。