Ansible lintを実行するDockerイメージを作成する

こちらの記事は[Ansible lint Advent Calendar 2022](https://qiita.com/advent-calendar/2022/ansible-lint) 7日目の記事になります。

今回はAnsible lintを実行するDockerイメージのビルド方法について解説します。コードのサンプルは[こちら](https://github.com/docker-images-mamono210/ansible-lint)になります。

### なぜDockerイメージを作成するのか

* [pyenv](https://github.com/pyenv/pyenv)や[Virtualenv](https://virtualenv.pypa.io/en/latest/)でセットアップした環境が比較的壊れやすい（異論はあるかと思います）

* Red Hatが提供しているAnsible lintがインストールされた[Dockerイメージ](https://quay.io/repository/ansible/creator-ee)`quay.io/ansible/creator-ee`に[Ansible Community Package](https://docs.ansible.com/ansible/latest/reference_appendices/release_and_maintenance.html)が入ってない

* Asibleコンテンツの開発者が増えると同一バージョンのライブラリを開発者間のローカル環境で利用するのが難しくなる

もし`creator-ee`のイメージを利用する事に問題なければメンテナンスコストを考えるとそちらを利用した方がよいかと思います。

しかし現時点では[Ansible Community Package](https://docs.ansible.com/ansible/latest/reference_appendices/release_and_maintenance.html)が`creator-ee`のイメージにはインストールされていません。そのためAnsible lintの全ての機能を利用できないため現実的ではないかと思います。


### ファイル

以下のファイルを利用します。

* Dockerfile
* requirements.txt


```dockerfile:Dockerfile
FROM python:3.11.0-slim-buster as builder

# Install required packages
RUN apt-get update \
  && mkdir -p /usr/share/man/man1 \
  && pip3 install --upgrade pip \
  && apt-get install -y \
    apt ca-certificates curl git locales openssh-client sudo unzip

# Copy requirements.txt
COPY requirements.txt /tmp/

# Install Ansible lint and Ansible
RUN pip3 install --no-cache-dir --no-compile -r /tmp/requirements.txt

FROM python:3.11.0-slim-buster as production

# Install required packages
RUN apt-get update \
  && mkdir -p /usr/share/man/man1 \
  && pip3 install --upgrade pip \
  && apt-get install -y git

COPY --from=builder /usr/local/lib/python3.11/site-packages/ /usr/local/lib/python3.10/site-packages/
COPY --from=builder /usr/local/bin/ansible-lint /usr/local/bin/ansible-lint
COPY --from=builder /usr/local/bin/ansible /usr/local/bin/ansible
COPY --from=builder /usr/local/bin/ansible-config /usr/local/bin/ansible-config
COPY --from=builder /usr/local/bin/ansible-connection /usr/local/bin/ansible-connection
COPY --from=builder /usr/local/bin/ansible-galaxy /usr/local/bin/ansible-galaxy
COPY --from=builder /usr/local/bin/ansible-playbook /usr/local/bin/ansible-playbook

# Remove caches
RUN find /usr/ -name '__pycache__' -print0 | xargs -0 -n1 rm -rf \
  && find /usr/ -name '*.pyc' -print0 | xargs -0 -n1 rm -rf

WORKDIR /data
ENTRYPOINT ["ansible-lint"]
ENV ANSIBLE_FORCE_COLOR='1' PY_COLORS='1'
CMD ["--version"]
```

```text:requirements.txt
ansible-lint==6.9.1
ansible==7.0.0
```

### イメージをビルドする

`Dockerfile`と`requirements.txt`を同一ディレクトリへ配置し以下のコマンドでビルドします。

```bash
docker build -t <image-name>:<tag> .
```

### 使い方

Ansible lintを実行したいディレクトリで以下のコマンドを実行します。

```bash
$ docker run --rm -v $(pwd):/data <image-name>:<tag> <ファイル名>

# ディレクトリ全体
docker run --rm -v $(pwd):/data <image-name>:<tag> .
```

ファイル名を指定しない場合はバージョン情報が出力されます。

```bash
$ docker run --rm -v $(pwd):/data <image-name>:<tag>

ansible-lint 6.8.0 using ansible 2.13.6
```


### 簡単な解説

#### Ansibleは必ずインストールする

`pip3 install ansible-lint`でAnsible lintをインストールすると[Ansible core](https://docs.ansible.com/ansible/latest/reference_appendices/release_and_maintenance.html)しかインストールされません。これは[deprecated-module](https://ansible-lint.readthedocs.io/rules/deprecated-module/)や[fqdn](https://ansible-lint.readthedocs.io/rules/fqcn/)などの検査をする時に正しい結果が得られません。必ずAnsible（[Ansible community package](https://docs.ansible.com/ansible/latest/reference_appendices/release_and_maintenance.html)とも言われる）をインストールします。

#### productionイメージは`python:slim-buster`を利用する

`alpine`へPython 3をインストールして軽量化する方法もありますが手元で試してみた限りは実行速度が遅かったです。この件に関しての詳しい解説が「[仕事でPythonコンテナをデプロイする人向けのDockerfile (1): オールマイティ編 | フューチャー技術ブログ](https://future-architect.github.io/articles/20200513/)」にありましたのでシェアさせていただきます。

#### マルチステージビルドを採用しDockerイメージを軽量化

近年の流行りです。イメージを軽量化できます。

#### 環境変数に`ANSIBLE_FORCE_COLOR='1'`および`PY_COLORS='1'`を設定しログの表示に色をつける

好みです。

#### Ansible lint・Ansibleのバージョンは`requirements.yml`へ記述する

Dockerfileへ直接記述してもよいですがこの辺りはビルドの戦略によります。例えばDependabotが導入される場合は`requirements.yml`が必要になります。バージョニングが必要ない場合（常に最新のAnsible lintと[Ansible community package](https://docs.ansible.com/ansible/latest/reference_appendices/release_and_maintenance.html)を利用する場合）はDockerfile側にインストール処理をそのまま記述すると良いかと思います。

```dockerfile:Dockerfile
FROM python:3.11.0-slim-buster as builder

# Install required packages
RUN apt-get update \
  && mkdir -p /usr/share/man/man1 \
  && pip3 install --upgrade pip \
  && apt-get install -y \
    apt ca-certificates curl git locales openssh-client sudo unzip

# Copy requirements.txt
COPY requirements.txt /tmp/

# Install Ansible lint and Ansible
RUN pip3 install --no-cache-dir --no-compile -r ansible-lint ansible

FROM python:3.11.0-slim-buster as production

# Install required packages
RUN apt-get update \
  && mkdir -p /usr/share/man/man1 \
  && pip3 install --upgrade pip \
  && apt-get install -y git

COPY --from=builder /usr/local/lib/python3.11/site-packages/ /usr/local/lib/python3.11/site-packages/
COPY --from=builder /usr/local/bin/ansible-lint /usr/local/bin/ansible-lint
COPY --from=builder /usr/local/bin/ansible /usr/local/bin/ansible
COPY --from=builder /usr/local/bin/ansible-config /usr/local/bin/ansible-config
COPY --from=builder /usr/local/bin/ansible-connection /usr/local/bin/ansible-connection
COPY --from=builder /usr/local/bin/ansible-galaxy /usr/local/bin/ansible-galaxy
COPY --from=builder /usr/local/bin/ansible-playbook /usr/local/bin/ansible-playbook

# Remove caches
RUN find /usr/ -name '__pycache__' -print0 | xargs -0 -n1 rm -rf \
  && find /usr/ -name '*.pyc' -print0 | xargs -0 -n1 rm -rf

WORKDIR /data
ENTRYPOINT ["ansible-lint"]
ENV ANSIBLE_FORCE_COLOR='1' PY_COLORS='1'
CMD ["--version"]
```