Dockerfile の技術的負債についてまとめてみた

## はじめに

この記事は、[NTTコムウェア Advent Calendar 2021](https://qiita.com/advent-calendar/2021/nttcomware) 4日目の記事です。
NTT コムウェア 1 年目の東です。
この記事では Dockerfile における技術的負債の中でも、Self-Admitted Technical Debt (SATD)と呼ばれる開発者によって認知されている技術的負債についてまとめます。
この記事から得られる知見によって、Dockerfile にはどのような技術的負債があるかを知っていただき、技術的負債の少ない Dockerfile を作成する足がかりとなれば幸いです。

## 技術的負債とは

技術的負債とは、場当たり的な実装を借金することに見立てて作られた比喩表現です。
技術的負債は借金と同じで、開発当初（借金の借入当初）は改修（返済）が容易かもしれませんが、開発が進むにつれ、影響箇所が増えることによってどんどん改修が困難になっていきます。
そのため、技術的負債を作らない、または早めに返済してしまうことが重要になってくるのです。

## Self-Admitted Technical Debt (SATD)

技術的負債の中でも開発者が技術的負債であると認識しているものに関しては、Self-Admitted Technical Debt (SATD) と呼ばれます。
では、開発者はなぜ認識しているにもかかわらず、技術的負債を生んでしまうのでしょうか。
その答えとして、以下のような原因が考えられます。

- 納期に間に合わせるために開発を急がなければならない
- コードを書く知識が足りていないため、最適な実装方法を知らない
- 直前の仕様変更による準備不足

この記事を読んでいる方の中にも、上記のような理由のため、最善ではないと分かっていながらも次善的な実装を施し、技術的負債を混入させたことがあるという経験がある方は多いのではないでしょうか。
これらの技術的負債を生む要因の中でも「知識不足」に関しては、どのような部分が技術的負債になりやすいかや、類似する技術的負債に関する見識を広げることで、技術的負債を回避できるのではないかと考えます。

### SATD の判定方法

前項で SATD は「開発者自身」が認識している技術的負債であると説明しました。
それでは、開発者以外が SATD であると判断するにはどうすればよいでしょうか。
コードに記述されたコメントを見ればよいのです。
多くの場合、次善的な実装をする際にはコード内に`TODO`や`FIXME`などの文字列と一緒にコメントで「この部分のコードには問題がある」という旨が記されています。
そのため、ソースコード内のコメントを読むことで、SATD が検出できます。

## Dockerfile における SATD

今回は SATD の中でも Dockerfile における SATD にはどのようなものがあるかを紹介したいと思います。
Docker は 2013 年頃に開発された比較的新しいプラットフォームであり、Docker に精通している開発者が他のプラットフォームに比べて少ないと考えられることから、Docker イメージを構築するにあたり記述する Dockerfile にも多くの SATD が存在すると推測できます。

以下では、Dockerfile の SATD を体系的に分類し、それぞれの SATD の特徴を簡単に説明します。
コード例に関しては、実在する SATD の内容を参考に作成した架空のコードです。

### Dockerfile の保守を困難にする恐れのある SATD

#### 次善策による実装に関する SATD

より最適な手法があると分かっていながら、時間的要因などで、次善策を採用している SATD です。
`TODO`コメントとして書かれていることが多いです。

```lang=dockerfile
# TODO: 特定の環境でyum autoremoveを行うと、filesystemまで消えるバグがあるため、手動で削除する。解決策は調査。
RUN yum remove packagex
```

#### 処理の欠如に関する SATD

コンテナ内部で必要な処理の実装が未実装であることについて言及している SATD です。
この SATD は実装していないことが致命的な機能ではなく、未実装でも問題が少ない付属的な機能に多いです。

```lang=dockerfile
# TODO: submojuleはshallow cloneした方がいいかもしれないから調査する
RUN git submodule update --init --recursive
```

#### ベースイメージに関する SATD

ベースとしているイメージ自体にバグがあることに関する言及やベースイメージの変更を求める SATD です。
Docker ではベースイメージとして既存のイメージを継承できる機構が備わっています。そのため、開発者自身が開発している Dockerfile に問題は含まれていないものの、継承元に問題が含まれているというパターンが存在します。

```lang=dockerfile
# issueが解決されたらバージョンをあげる
FROM sampleimage:1.2
```

#### バージョンに関する SATD

パッケージマネージャでダウンロードするソフトウェアやツールのバージョン固定を促す SATD です。
バージョン固定に関しては[Docker のベストプラクティス](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/)でも言及されており、パッケージの予期せぬ変更によるビルドの失敗を防ぐためにバージョンを固定するべきだと言われています。

```lang=dockerfile
# TODO: バージョンを固定する
RUN npm install sample
```

### テストに関する SATD

#### 真正確認に関する SATD

コンテナ内で利用するバイナリファイルをダウンロードした際に、そのバイナリファイルのハッシュ値が正しいかどうかを PGP や sha256sum などで確認することを真正確認と呼びます。
その真正確認の不足に言及した SATD です。
この真正確認は中間者攻撃を防ぐために行われ、セキュリティ面を担保するために、Docker の公式イメージなどではよく行われています。

```lang=dockerfile
# TODO: sha256の値を検証し、検証できたらURLの末尾に.sha256を追加する
RUN wget -O sample.tgz https://example.com/oss/sample.tar.gz
```

#### テストを行うための改良に関する SATD

テスト手法の改善に関する SATD です。
テスト自体の改善ではなく、テストを効率的に行うための改善に言及した SATD になっています。

```lang=dockerfile
# TODO: 1ファイルで複数のコンテナを立ち上げているが、テストにしか必要ないものが多いので、分離する
FROM sample1

RUN yum ...

FROM sample2
COPY /build /
```

### バグに関する SATD

#### バグ回避のための強引な措置に関する SATD

コンテナ内部で使用するソフトウェアやパッケージにバグが含まれていた場合に、それらのバグに対する回避策を実行している旨を記した SATD です。
中にはかなり強引な改変を行ってバグを回避している例もあるため、このような SATD が存在するイメージをベースイメージとして扱う場合は注意が必要です。

```lang=dockerfile
# FIXME: sampleのバージョンを5.1にするとビルドが落ちるので、治るまで5.0を指定しておく
RUN pip install sample==5.0
```

#### 将来的なバグに関する SATD

現在は顕在化していないものの、将来的に発生する可能性があるバグに言及した SATD です。
コンテナ内部で使用しているパッケージが後方互換性のないアップデートを行うことにより、現状しようしているコマンドが使用できなくなりバグとして現れます。
この SATD はあくまで現状は問題になっていないため、対応が後回しにされがちです。

```lang=dockerfile
# バージョンが期限切れになるとURLを変えなければならないかも
RUN curl -OL https://example.com/oss/{VERSION}/sample.tar.gz
```

### Docker のベストプラクティスに反した設計に関する SATD

#### イメージサイズ削減に関する SATD

Docker のベストプラクティスとしてイメージサイズは極力小さくすることが良いとされています。
そのため、イメージのサイズ削減を要求する SATD が存在します。
例えば、Docker では`RUN`命令を分けて書くと、その分層が増え、イメージサイズも大きくなるため、`RUN`命令で実行する shell コマンドは極力一まとめにすることを推奨されています。
また、この SATD もコンテナの実行には直接影響を与えることが少ないため、対応が後回しにされがちです。

```lang=dockerfile
# TODO: 不要なファイルを削除する
```

### 開発の特定プロセスにおける問題に関する SATD

#### デプロイに関する SATD

デプロイの際に起こり得る問題に言及した SATD です。
本番環境用にイメージを使用する際の注意喚起を行っている例がありました。

```lang=dockerfile
# このconfigを本番環境で使わないでください
CMD ["server", "-dev"]
```

#### Dockerfile 自体のレビューに関する SATD

Dockerfile 自体のレビューを他の開発者に求めている SATD です。
この SATD では、現状の実装方法が最適かどうか開発者自身には判断がつかないため、他の開発者にレビューを求めている状況になっています。

```lang=dockerfile
# 書き方が正しいか自信がないので、レビューをお願いします。
```

## おわりに

本記事では、Dockerfile における SATD について紹介しました。この記事を読んで少しでも技術的負債に関する見識が広がったり、今後の Dockerfile の開発に役立てていただいたりしていただければ幸いです。

以上でこの記事を締めたいと思います。
最後まで読んでいただき、ありがとうございました。

---

- [コンテナ仮想化技術における Self-Admitted Technical Debt の調査](https://www.ieice.org/ken/paper/20201019vCA6/)