DockerDesktopアプリケーションをシェルスクリプトからいい感じに起動させる

# Introduction
Docker Desktopが起動してなかったら起動させたいし、リソースセーバーが動いてたら再起動をシェルスクリプトでやりたい！をやったというお話です。

さて、大学の講義で演習に使用しているプログラムがあります。
Docker Desktopが起動している前提で動くものなのですが、学生から「なんか実行できないんですが…」と言われて確認すると…
「Docker、起動してないやんけ…！！！」
となることが多いTA(教育補助の学生)さんです。こんにちは。
そんなのにいちいち対応するのは~~とっても面倒~~大変なので、コンテナ起動を仲介してる.command(中身はbashスクリプト)からよしなにDockerくんを起動させてしまおう！というのが今回の趣旨です。

# 想定する環境
- Mac系のPC
- Docker Desktop導入済み
- AppleScriptが使える（正確にはosascript）

# 結論
```bash:Docker_autorun.command
# 現在実行されているアプリケーション名を取得
RUN=`osascript -e 'tell application "System Events" to get name of (processes where background only is false)' | grep -c 'Docker Desktop'`

# Docker Desktopがアプリケーションとして起動してないと認識されている場合
if [ ${RUN} = 0 ]; then
    echo "Docker Desktop is not Started."

    # Dockerがリソースセーバーモードで起動している場合
    DOCER_PROCESS_COUNT=`ps aux | grep docker | grep -v grep | wc -l`
    if [ ${DOCER_PROCESS_COUNT} -gt 0 ]; then
        echo "Docker kernel is run Resource Saver Mode... "
        pkill -KILL -f Docker
        pkill -KILL -f docker
        echo "Docker application killed."
    fi

    open -g -a /Applications/Docker.app

    # 全てのプロセスの起動を待つ
    DOCER_PROCESS_COUNT=`ps aux | grep docker | grep -v grep | wc -l`
    until [ ${DOCER_PROCESS_COUNT} -gt 6 ]
    do
        sleep 1
        echo "Preparing to launch Docker Desktop ..."
        DOCER_PROCESS_COUNT=`ps aux | grep docker | grep -v grep | wc -l`
    done
    # UI起動後の読み込み待ち
    sleep 5
    
    echo "Docker Desktop is running..."

# Docker Desktopがアプリケーションとして起動していると認識されている場合
else
    echo "Docker Desktop is running..."
    
fi

```

# 全体の挙動
- Docker Desktopがアクティブに起動していれば、「Docker Desktop is running...」と出力して終了
- Resource Saver Modeで起動していれば、一旦全部のプロセスをkillして再起動
- 起動していなければ、起動
- 全部のプロセスが起動して、UIのコンテナ情報読み込みが終了するまで待機した後、「Docker Desktop is running...」と出力して終了

全体はこんな感じです。

# 詳細と解説
## 実行されているアプリケーション名の取得
```bash
RUN=`osascript -e 'tell application "System Events" to get name of (processes where background only is false)' | grep -c 'Docker Desktop'`
```
起動していたら1、していなかったら0がRUNに格納されます。

どうにかして取れないかな〜〜〜と思っていた時にこちらのstackoverflowを見つけて実行したら取れました。

https://stackoverflow.com/questions/7929872/get-list-of-running-apps-on-mac-os-x-in-bash

:::note info
osascriptとは？
AppleScriptをターミナルで実行できるコマンドです。
今回はその後ろの方にあるシングルクオート内がAppleScriptです。
:::
後ろの方の``grep -c 'Docker Desktop'``は得られた結果をパイプで渡して、一致した個数とってくるようにしているものです。

## Resource Saver Modeで起動している時はプロセスをkill

```bash:
DOCER_PROCESS_COUNT=`ps aux | grep docker | grep -v grep | wc -l`
if [ ${DOCER_PROCESS_COUNT} -gt 0 ]; then
    echo "Docker kernel is run Resource Saver Mode... "
    pkill -KILL -f Docker
    pkill -KILL -f docker
    echo "Docker application killed."
fi
```

### Resource Saver Modeとは
![メニューバーのDockerアイコン](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/454084/f5abafb1-5aba-dd92-efae-c91b51ec2f48.png)
↑これに見覚えがある人、この状態がResource Saver Modeです。

https://docs.docker.com/desktop/use-desktop/resource-saver

>It significantly reduces Docker Desktop's CPU and memory utilization on the host by 2 GBs or more, by automatically stopping the Docker Desktop Linux VM when no containers are running for a period of time.

つまり、一定時間アクティブなコンテナがなければ自動的にCPUとメモリの使用を制限してくれるというモード。

:::note warn
このモードの最中は、コンテナの起動、終了とかの操作、特に`docker compose`は何があったの？と思うくらい遅くなります。
:::

### killする必要あるの？
実はリソース管理面ではかなりありがたい機能なんですが、講義で使うようなパターンだといくつか落とし穴があります。
- 下手すると1週間起動しっぱなしの学生がいる
→次週の講義までに他のアプリケーションによって設定変更とかあるとDockerくんが修繕要求出してくる
- 何かしらのエラーが出ても正常だと思い込んで使おうとする
→明らかに止まってて実行できてないのに「ここのコードがうまく動かなくて」と言われて発覚が遅れる
- たまに`/Application/DockerDesktop 2.app`なる複製体がいる
→うまく動かないと入れ直ししようとするんだが、その時に前のものを消さずに.dmgからコピーする信じられない挙動をする
- 実行に時間がかかるとそれだけでターミナル自体を落としてしまうことがある
→そのまま待ってればできるはずなのに待ってあげないので「なんか起動しなくて…」と言われる（時間の無駄）

知らないって、怖いですよね…。
これらの事象を起こさないようにするためには、そもそもResource Saver Modeを切ればいいわけです。

環境構築もままならない子がいるのに？？？意味わからんとこ弄られたら困るが？？？
ということで、こっちでプロセスをkillします。恐ろしいことにそっちの方が安全だからです。

:::note info
そんなことない人はこっちの手順の方がいいです。
[Docker for Mac/Windowsが妙に遅い時はResource Saverをオフに](https://zenn.dev/moozaru/articles/f8632ad34feb79)
:::

### DOCER_PROCESS_COUNT
```bash
ps aux | grep docker | grep -v grep | wc -l
```
これは「CPUとか割り当てられて現在実行中のプロセスから（`ps aux`）、dockerとプロセスに入ってるものを抽出して（`grep docker`）、その中から抽出に使用してるgrepプロセスを抜いて（`grep -v grep`）、改行の数を数える（`wc -l`）」という処理です。
つまりコマンドとしては「Dockerの現在実行中のプロセスの数を数える」というものになります。

プロセスと前述のアプリケーション起動確認のコマンドの状況だけを見れば、Resource Saver Modeは「アプリケーションとしてDockerは起動してないと認識されてるけど、プロセスはある」状況です。
なのでプロセスの個数が0以上であれば、Resource Saver Modeとなります。

:::note warn
後述のcom.docker.virtualizationプロセスを検知するという手段も考えたんですが、「kernelのプロセスがない」を判定するよりも、プロセスの個数のみを見た方が簡単でミスりにくいです。
プロセスが本当に何も起動していない時はもちろんcom.docker.virtualizationプロセスがなく、「Dockerのプロセスはあるがkernelがない」は直接的で分かりやすいですが、条件式としては複雑になります。
プロセスを強制的にkillするので、無意味に叩くのはちょっと…
:::

:::note info
bashでのif文については[【シェルスクリプトBash】 if 文（test文）のオプションまとめ](https://qiita.com/yaaabu51/items/8758447fa672288f4757)を参考にしました。
:::

### プロセスのkill

```bash
pkill -KILL -f Docker
pkill -KILL -f docker
```

`pkill`は名前を指定してプロセスをkillするコマンドです。
こちらを参考にしました。

https://qiita.com/14021939/items/05c88cbd64f896116bec

Docker Desktopのもつ基本プロセスは以下のもの。
- com.docker.build
- com.docker.dev-envs -watchdog
- /Applications/Docker.app/Contents/MacOS/com.docker.backend
- /Applications/Docker.app/Contents/MacOS/com.docker.backend run
- /Applications/Docker.app/Contents/MacOS/Docker
    - UIの表示に使われてるものっぽい
    - メニューバーとウィンドウとか起動してるUIの分ある
- /Applications/Docker.app/Contents/MacOS/com.docker.virtualization
    - Docker Desktop Linux VMのカーネルと思われるもの

:::note info
`ps aux | grep docker | grep -v grep`で確認できます。
:::

これらのうち、/Applications/Docker.app/Contents/MacOS/com.docker.virtualization以外がResource Saver Modeの時に存在する可能性のあるプロセスです。
わざわざDockerとdockerでやっているのはプロセス名が異なるから。
先にUIをkillしてから本体のプロセスをkillしています。

## アプリケーションの起動
```bash
open -g -a /Applications/Docker.app
```
こちらの記事を参考にしました。

https://qiita.com/aokiplayer/items/74c7adc3d951a6fbd41f

## 全プロセスの起動を待機する
```bash:
DOCER_PROCESS_COUNT=`ps aux | grep docker | grep -v grep | wc -l`
until [ ${DOCER_PROCESS_COUNT} -gt 6 ]
do
    sleep 1
    echo "Preparing to launch Docker Desktop ..."
    DOCER_PROCESS_COUNT=`ps aux | grep docker | grep -v grep | wc -l`
done
```

### プロセス数の再計算
```bash
DOCER_PROCESS_COUNT=`ps aux | grep docker | grep -v grep | wc -l`
```
「Resource Saver Modeで起動している時はプロセスをkill」の項目で書いた通り。

### プロセス数が6より多くなるまで待機する
再掲になるがDocker Desktopのもつ基本プロセスは以下。
- com.docker.build
- com.docker.dev-envs -watchdog
- /Applications/Docker.app/Contents/MacOS/com.docker.backend
- /Applications/Docker.app/Contents/MacOS/com.docker.backend run
- /Applications/Docker.app/Contents/MacOS/Docker
    - UIの表示に使われてるものっぽい
    - メニューバーとウィンドウとか起動してるUIの分ある
- /Applications/Docker.app/Contents/MacOS/com.docker.virtualization
    - Docker Desktop Linux VMのカーネルと思われるもの

ちゃんと全部が起動できれば上4つとメニューバー、メインとなるウィンドウ、カーネルで7つのプロセスが起動するはずです。
それらが全部起動するまで1秒待機して、プロセス数を更新し続けます。

:::note info
bashの繰り返しについては[とほほのBash入門](https://www.tohoho-web.com/ex/shell.html)を参考にしました
:::

## UI起動後の読み込み待ち
```bash
sleep 5
```
Docker Desktopはメインとなるウィンドウが起動した後、ローディングがあってからコンテナ一覧とかが表示されると思います。
読み込み中とか処理中に、こっちから割り込みすると予期せぬ挙動をすることがあるので5秒（適当に指定した）待機するようにしています。

:::note warn
PC本体の処理状況やスペックによっては5秒では短いかもしれない。
:::


# 全体像（再掲）
ということで、以上のことをやっているのがDockerDesktop.appをいい感じで自動起動させる処理です。
```bash:Docker_autorun.command
# 現在実行されているアプリケーション名を取得
RUN=`osascript -e 'tell application "System Events" to get name of (processes where background only is false)' | grep -c 'Docker Desktop'`

# Docker Desktopがアプリケーションとして起動してないと認識されている場合
if [ ${RUN} = 0 ]; then
    echo "Docker Desktop is not Started."

    # Dockerがリソースセーバーモードで起動している場合
    DOCER_PROCESS_COUNT=`ps aux | grep docker | grep -v grep | wc -l`
    if [ ${DOCER_PROCESS_COUNT} -gt 0 ]; then
        echo "Docker kernel is run Resource Saver Mode... "
        pkill -KILL -f Docker
        pkill -KILL -f docker
        echo "Docker application killed."
    fi

    open -g -a /Applications/Docker.app

    # 全てのプロセスの起動を待つ
    DOCER_PROCESS_COUNT=`ps aux | grep docker | grep -v grep | wc -l`
    until [ ${DOCER_PROCESS_COUNT} -gt 6 ]
    do
        sleep 1
        echo "Preparing to launch Docker Desktop ..."
        DOCER_PROCESS_COUNT=`ps aux | grep docker | grep -v grep | wc -l`
    done
    # UI起動後の読み込み待ち
    sleep 5
    
    echo "Docker Desktop is running..."

# Docker Desktopがアプリケーションとして起動していると認識されている場合
else
    echo "Docker Desktop is running..."
    
fi

```

無駄処理あるかも〜と思いつつ、一旦できていそうなのでこれで。
issueにこれ入れたの誰だ（私だな…）と思いつつの開発でしたができてよかった〜！