Chainguardイメージを布教したい

Dockerのイメージの中で、
サイズが小さくてセキュリティが高い印象を受ける有名どころとしては、
Alpineなどがあるかと思いますが、
もっとこれらに特化したイメージがあります。

それがChainguardです。

もしかすると、そこまで知名度があるわけではないかもしれませんが、
イメージサイズとCVEの少なさは随一です。
高度なセキュリティが特徴です

Dockerのイメージサイズを縮小するのが趣味な人や、
セキュリティ大好きマンの方にはぜひとも試してほしいDockerイメージです。

## Chainguardとは

そもそもChainguardとはなんぞやって話ですが、
Chainguard(イメージ)は、Chainguard社によって提供されている、
高いセキュリティを誇るコンテナイメージ群です。

dockerhubでChainguardと検索すると、
500以上のイメージがヒットすることがわかります。

https://hub.docker.com/search?q=Chainguard

Chainguardの特徴は、やはり従来のコンテナイメージの構築と運用に伴う
セキュリティリスクを軽減していることでしょう。

下記が公式のページです。

https://www.chainguard.dev

0 CVE containerと打ち出していることからも分かる通り、
セキュリティやコンテナイメージサイズの縮小などに強みを持った、
イメージであることがわかります。

ちなみに、CVEというのは、
Common Vulnerabilities and Exposures の略称で、共通脆弱性識別子と言ったりする、
要するに脆弱性のリストのようなものかと思います。

https://www.sompocybersecurity.com/column/glossary/cve

https://www.redhat.com/ja/topics/security/what-is-cve

そんな脆弱性のリストが0ということからも分かる通り、
Chainguardが一般的なDockerイメージと比べて高いセキュリティを誇っていることが伺えます。

### Chainguardのセキュリティ

すでに述べたようにChainguardの大きな魅力は、
そのセキュリティ機能です。

その中でも、Chainguardイメージは、
Wolfiというアンディストリビューション(undistro)なOSをベースとしていることが
大きな特徴でしょう。

https://edu.chainguard.dev/open-source/wolfi/overview

このWolfiというのがまた結構面白いOSだなって思いまして、
これについてのドキュメントを読むだけでも普通に勉強になります

distrolessなイメージとして、
不要なソフトウェアが入っていないことも特徴です。

https://edu.chainguard.dev/chainguard/chainguard-images/getting-started-distroless

Alpineとかを使ったことがある方は、
コンテナサイズが小さい分、何も入っていないから、
自分で結構色々インストールしなきゃいけなかったみたいない印象があるかもしれません。
ChainguardイメージはそんなAlpineよりもさらに無駄が削ぎ落とされています。

というのも、Chainguardは、下記のような特徴をもっています。
(※公式のやつを訳して貼ってあるだけなので、公式の方を見てもらったほうがいいかもです)

- 不要なソフトウェアを排除したミニマルなデザイン
- 自動化ビルドにより、イメージの最新性と利用可能なセキュリティパッチを確保していること
- イメージ内のすべてのアーティファクトの出所を証明する高品質のビルドタイム SBOM (ソフトウェア部品表)
- Sigstore によって提供される検証可能な署名
- Cosign と apko による再現可能なビルド

正直、完全に理解しきれていない部分もありますが(SigstoreとかapkoとかCosignとか)
基本的にはもともと不要なソフトウェアを排除したミニマルデザインなのに、
その中でも使われている技術の信頼性は担保しているという、
本当にセキュリティに特化しているなという印象を受けました。

こうした特徴から、Chainguardはセキュリティに特化していることはもちろん、
コンテナサイズの縮小にも寄与したコンテナプラットフォームとして、非常に魅力的だと考えます。

## 他のDockerイメージとの違い
さて、そんなChainguardですが、
よく使用されるDockerイメージと何がどれぐらい違うのでしょう。

なんとなくセキュリティがすごそうということはわかりましたが、
まだいまいちイメージがわかないかと思うので、
ここでRubyのChainguardイメージ、RubyのAlpineイメージ、一般的なRubyのDockerイメージの
3つのコンテナを立ててみようと思います。

### Rubyの一般的なDockerイメージの環境構築

まずは、普通のDockerイメージの方を環境構築します。
簡易的なもの(というか脆弱性スキャンがメイン)なので、
今回はsinatraを使おうと思います。
(そういえば最近はsinatraをあまり見かけなくなった)

まずは起動するapp.rbを作成します

```rb:app.rb
require 'sinatra'

set :bind, '0.0.0.0'

get '/' do
  'Hello, from Docker!'
end
```

次にGemfileを作成します。
sinatraだけだとエラーになるので、rackupも追加します

```rb:Gemfile
source 'https://rubygems.org'
gem 'sinatra'
gem 'rackup'
```

次にDockerfile.rubyを作成します

```Dockerfile:Dockerfile.ruby
FROM ruby:latest

WORKDIR /app

COPY Gemfile ./
RUN bundle install

COPY . .

CMD ["ruby", "app.rb"]
```

まあよくあるDockerファイルの記法という感じですね。

あとは、buildとrunをします。

```bash:
docker build -t ruby-demo -f Dockerfile.ruby .
docker run -d -p 5000:4567 --name ruby-demo-container ruby-demo
```
`docker ps`を行って、問題なくコンテナが立ち上げっていればOKです

### RubyのChainguardイメージの環境構築

次にChainguardの方も環境構築します。

Gemfileとapp.rbは共通なので、
Dockerfile.chainguardだけ作成します

```Dockerfile:Dockerfiile.chainguard
FROM cgr.dev/chainguard/ruby:latest-dev as builder

ENV GEM_HOME=/work/vendor
ENV GEM_PATH=${GEM_PATH}:/work/vendor

COPY Gemfile /work/
RUN gem install bundler && bundle install

FROM cgr.dev/chainguard/ruby:latest

ENV GEM_HOME=/work/vendor
ENV GEM_PATH=${GEM_PATH}:/work/vendor

COPY --from=builder /work/ /work/
COPY app.rb /work/

ENTRYPOINT [ "ruby", "app.rb" ]
```

さて、Chainguardの方ですが、
こちらは少し書き方がトリッキーになります。
普通のRubyイメージの書き方と同じようにすると、
画像のようにshがない的なエラーになって
bundle installとかができません。

これは最初にちらっと触れましたが、
distrolessなイメージとして
シェルとかパッケージマネージャーも省かれているので、
まあ当然といえば当然ですね。

なのでFROMでは、cgr.dev/chainguard/ruby:latest ではなくて、
cgr.dev/chainguard/ruby:latest-dev を指定します。

ただ、これだけでもダメで、
今度はrubyコマンドがない的なエラーにも遭遇するので、
multi stage build構成にします。

この辺の詳細はこのあたりに書かれています。

https://edu.chainguard.dev/chainguard/chainguard-images/getting-started/ruby

書きなれたDockerfileの記法とは少し異なる書き方が必要なので、
このあたりは慣れが必要かもしれません。

```bash:
docker build -t chainguard-ruby-demo -f Dockerfile.chainguard .
docker run -d -p 5001:4567 --name chainguard-ruby-demo-container chainguard-ruby-demo
```

さて、`docker ps`で無事立ち上がればOKです

### RubyのAlpineイメージの環境構築

さて、３つ目にAlpineの方も行おうと思います。

Gemfileとかapp.rbは同じく共通なので、
Dockerfile.alpineをまず作成します

```Dockerfile:Dockerfile.alpine
FROM ruby:alpine

WORKDIR /app

COPY Gemfile ./
RUN bundle install

COPY . .

CMD ["ruby", "app.rb"]
```
alpineの場合、何か別途インストールしないとダメかなと思いましたが、
意外にも普通のRubyイメージと同じ構成のDockerfileで問題なく立ち上がりました。

```bash
docker build -t ruby-demo-alpine -f Dockerfile.alpine .
docker run -d -p 4567:4567 --name ruby-demo-alpine-container ruby-demo-alpine
```
何も入っていないように思えたAlpineも、
比べてみるとまだ色々入っている方なのかもしれないと感じる瞬間でした

ていうかここまで環境構築していて思ったのが、
普通にDocker-composeでやれよっていうね。

## 各イメージの脆弱性スキャン

さて、各イメージの環境構築をしたので、
脆弱性スキャンやイメージサイズを比較してみます。

まず、イメージのサイズを比較してみます。

```bash
docker images | grep demo
```

ruby-demoが1.08GB、ruby-demo-alpineが170MB、
chainguardが51.8MBで、
chainguardが一番小さかったです

うん、ruby-latest大きすぎ

### trivyを使った脆弱性スキャン
次に、trivyを使用して、イメージのセキュリティスキャンも実行してみます
trivyがなければ、homebrewとかでインストールしておきます。

https://github.com/aquasecurity/trivy

```bash
brew install trivy

trivy image ruby-demo
trivy image ruby-demo-alpine
trivy image chainguard-ruby-demo
```

とりあえず、ruby-demoが笑っちゃうぐらい脆弱性がありました。
chainguardは0で、alpineは1個でした

今回始めて知りましたが、
ruby-latestに、python3とかgitも入っていました。

ワンチャン、ruby-latestで、
pythonのサーバーを構築して、
gitでゴニョゴニョするみたいなお遊びができるじゃんと発見した瞬間でした。
(ていうかなんでpython3なんか入っているんだ)

### grypeを使った脆弱性スキャン
grypeの方でも、脆弱性スキャンを行ってみました。
結果は、trivyとほぼ同様だったので、
割愛します。

詳細はこちらをどうぞ

https://reisuta.com/chainguard/

### Docker Scoutでの比較

もうtrivyやgrypeを使って比較したので、
結果は見えていますが、
最後にdockerhubのScoutを使って見ようと思います。

結論ですが、これでやると、
alpineの脆弱性が5個ぐらいに増えました。

chainguardは相変わらず0(すげえなおい)

詳細はこちらをご覧ください

https://reisuta.com/chainguard/

## まとめ

有名どころのDockerイメージと
Chainguardイメージを主にイメージサイズと脆弱性の観点で
比較してみました。

Chainguardイメージは、やはりその高いセキュリティと
極限にまで縮小されたイメージサイズが特徴であり、
それらが大きな魅力だと私は感じました。

何気なく使っている、Dockerの普通のイメージとかが、
以下に肥大化したソフトウェアを内包していて、
多くの脆弱性を抱えているかがわかりました

Chainguardは、日本語での情報がまだ多くはなく、
通常のDockerfileに比べ、ややトリッキーな記法や、
Wolfi OSやdistroless、undistroといった、
人によっては馴染みが薄い概念も多くあるので、
技術的キャッチアップコストは、通常のDockerイメージよりは大きくなるかもしれませんが、
それを払ってでも、積極的に採用したい魅力的なDockerイメージだと思います。

それでは良い、Dockerイメージライフを