dockerコンテナでcron, sshできるようにする

# TL;DR
[完成したdockerfile](#完成したdockerfile)

# 背景
遊休パーツがあったので、ubuntuマシンを1台組んだ。
今後、環境構築は死ぬほどやりたくなかったので、
dockerにて管理することにした。この記事はその時の備忘録となる。
ラズパイで動いていたもの、AWSで動いていたもの、ここで一元管理したい。
リスク分散？知らないな....

# 前提とか
### ホストマシンのOS
Ubuntu 24.04 LTS
### 作るコンテナで何をするつもり?
- cronで指定時間にファイルをDLするスクリプトを実行する。
- sshで保存したファイルを見に行けるようにしておく。

# 1. 公式ドキュメントを読んでみる
ベストプラクティスのページを読んで考え方について把握しておく。
後でぐちゃぐちゃになった環境で困るのは自分だから。
ちなみに日本語化プロジェクトがあるので、最初はここを読むと良い気がする。



### ベストプラクティス
[Dockerfile を書くベストプラクティス — Docker-docs-ja 24.0 ドキュメント](https://docs.docker.jp/develop/develop-images/dockerfile_best-practices.html)
理由、経緯は省略して、ここからいくつか引用する

- __[各コンテナはただ１つだけの用途を持つべきです。](https://docs.docker.jp/develop/develop-images/dockerfile_best-practices.html#decouple-applications)__ 

- __[コンテナは、可能な限り一時的（ エフェメラルephemeral ）であるべきです。](https://docs.docker.jp/develop/develop-images/dockerfile_best-practices.html#create-ephemeral-containers)__

- __[不要なパッケージのインストール禁止](https://docs.docker.jp/develop/develop-images/dockerfile_best-practices.html#dont-install-unnecessary-packages)__

不要なパッケージを入れることに関しては「禁止」とかなり強い言葉を使っており、
笑いそうになったが「とりあえずあったら便利だし入れとくか!」のようなナイーヴな考え方は捨てろと言っている。お前のことだぞ。(自戒)
(ちなみに[以前のドキュメント](https://docs.docker.jp/engine/articles/dockerfile_best-practice.html#id3)には「不要なパッケージのインストールを避ける」と書いており、今よりだいぶ優しい。何があったんだ。)

以上を念頭において「あっ、俺が今やっていることは異常かもしれない」と自覚しながら作っていく。

# 2. NETWORKS作成
LAN内の他の端末からもアクセスしたかったため、
__ブロードキャストドメインはホストマシンと同一にする。__
設定にはmacvlanを使用する。
[macvlan ネットワークの使用 — Docker-docs-ja 24.0 ドキュメント](https://docs.docker.jp/network/macvlan.html)

ホストマシンが使用している物理ネットワークインタフェース名、subnet、gatewayをあらかじめメモしておく。
```shell:インタフェース名確認
ip addr
```

上記でメモっておいた各種値を確認しながら、コンテナに使用するネットワーク設定を作成する。
```shell:mynet作成
docker network create -d macvlan --subnet=192.168.1.0/24 --gateway=192.168.1.1 -o parent=enp4s0 mynet
```

ここでmacvlanを使用して作成できるもののイメージは以下ページ引用

[Dockerでmacvlanを使う | Reafnex](https://reafnex.net/it/it-container/how-to-use-docker-macvlan/)
> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216625/d9f740ce-a4af-1e77-50dc-4931d3b8b0d5.png)

俺がここで何を言いたいのかというと、
__ホストマシンとポートは共有しているため、外部とやり取りする場合はポートフォワード系のケアが必要__
になるということ。

:::note info
後述するssh設定では、コンテナ上のsshで使用するポートを変更しています。
:::

# 3. Dockerfile作成
### sshの設定を行う
ホストマシンでもsshを使用している場合、ポート変更は必須です。

```dockerfile:ssh
RUN apt install openssh-server -y
# 使用するポートを2223へ変更
RUN sed -i 's/#*Port 22/Port 2223/' /etc/ssh/sshd_config
# 公開鍵認証を使用した接続を許可
RUN sed -i 's/#*PubkeyAuthentication [a-z]*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
# パスワードを使用した接続を許可
RUN sed -i 's/#*PasswordAuthentication [a-z]*/PasswordAuthentication yes/' /etc/ssh/sshd_config
```

ちなみに公開鍵認証を有効した場合、公開鍵は ~/.ssh/authorized_keys に置きましょう。

### cronの設定を行う
```dockerfile:cron
# cron と nano
RUN apt install cron nano -y
# UTC であることに注意
RUN (crontab -l -u user; echo "00 16 * * 02 ur/file.sh")| crontab -u user -
```
なんか変な書き方してるので少し説明します。

| オプション | 効果 |
|:-:|:-:|
| -l  | 一覧出力 |
| -u | 実行ユーザ指定  |
| - |  標準入力受付 |

「一覧出力」 + 「echoで追記した文字列」を標準入力でcrontabに流し込む。
って感じです。

### コンテナ起動時にssh/cronも起動させる
以下スクリプトを用意して、dockerfileの最後の方で実行するように書きます。
```shell:startup.sh
#!/bin/sh

# Start the ssh server
/etc/init.d/ssh start

# Start the cron
service cron start

# Execute the CMD
exec "$@"
```

```dockerfile:dockerfileの最後の方
# startup
COPY startup.sh /startup.sh
RUN chmod +x /startup.sh
ENTRYPOINT ["/startup.sh"]

CMD ["/bin/bash"]
```
これでコンテナ起動時にスクリプトが実行されます。
最後のCMDは、コンテナ起動後すぐに落ちてしまわないようにするためです。
コンテナはやることがなくなったら落ちるので。

### 完成したdockerfile
すこしずつ切り取られると理解しにくいと思うので一応全体を載せときます。
説明していない部分、伏せてる部分もあるので注意。
```dockerfile:dockerfileの最後の方
FROM ubuntu:latest

RUN apt update && apt dist-upgrade -y && apt autoremove


# add user
# パスワードは手動で設定が必要
ARG USERNAME=hxbdy
ARG GROUPNAME=hxbdy
ARG UID=1001
ARG GID=1001
RUN groupadd -g $GID $GROUPNAME && useradd -m -s /bin/bash -u $UID -g $GID $USERNAME

# apt install
# cron
RUN apt install cron nano -y
# ssh
RUN apt install openssh-server -y
# git
RUN apt install git -y


# SSH
# 鍵は手動で追加が必要
RUN sed -i 's/#*Port 22/Port 2222/' /etc/ssh/sshd_config
RUN sed -i 's/#*PubkeyAuthentication [a-z]*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
RUN sed -i 's/#*PasswordAuthentication [a-z]*/PasswordAuthentication yes/' /etc/ssh/sshd_config


# make workspace
ARG WORKSPACE=/home/$USERNAME/workspace
WORKDIR $WORKSPACE
RUN chmod 777 $WORKSPACE


USER $USERNAME


RUN git clone https://github.com/???/???.git


# cron
# UTC であることに注意
RUN (crontab -l -u hxbdy; echo "00 16 * * 02 $WORKSPACE/???.sh")| crontab -u hxbdy -


# startup
USER root
COPY startup.sh /startup.sh
RUN chmod +x /startup.sh
ENTRYPOINT ["/startup.sh"]

CMD ["/bin/bash"]

```
以上。ビルドして完成。

# 4. IMAGES作成
あらかじめ作成しておいていたネットワーク設定を使い、ホストマシン起動と同時にコンテナも実行するコマンドはこうだ～～～
```shell:実行
docker run -it --name ubuntu --hostname="docker-ubuntu" --network mynet --ip 192.168.100.111 --restart unless-stopped ubuntu
```

### 疎通確認
windows powershellから確認した。
```powershell:実行
 Test-NetConnection 192.168.100.111 -Port 2223
```
色々出てくるけど、
TcpTestSucceeded : True
となっていればOK



# 逆引き目次
### コンテナ起動直後にスクリプトを実行したい
- dockerfileでENTRYPOINTを用意してスクリプトを指定する
    - [コンテナ起動時にssh/cronも起動させる](#コンテナ起動時にssh/cronも起動させる)


### sshできない
- ホストマシンと使用ポートがバッティングしている
    - [sshの設定を行う](#sshの設定を行う)
- ユーザのパスワードを設定していない
- ユーザの公開鍵を置いていない


### コンテナがすぐ落ちる
- [コンテナ起動時にssh/cronも起動させる](#コンテナ起動時にssh/cronも起動させる)


### タイムゾーンを変更しても次回起動時UTCに戻ってしまう
- 諦める
    - 自分はこっち。楽な方へ楽な方へ....
    - cronなどはUTCで設定する。
- いやいやいや、なんとかしたい。
    - このあたりを確認する
        - [【即解決】LinuxとWindowsをデュアルブートさせたら時刻がずれる現象の解決方法 #Ubuntu - Qiita](https://qiita.com/Juice/items/63279e93f818ba74312d)
        - [Linuxで時刻設定後、ハードウェア再起動後や電源投入後に日時ずれが生じる場合の対応 #Linux - Qiita](https://qiita.com/tabimoba/items/f9c4f9024f7c3ff65360)

# 参考
[Docker ドキュメント日本語化プロジェクト — Docker-docs-ja 24.0 ドキュメント](https://docs.docker.jp/)