Dockerのイメージの中で、
サイズが小さくてセキュリティが高い印象を受ける有名どころとしては、
Alpineなどがあるかと思いますが、
もっとこれらに特化したイメージがあります。
それがChainguardです。
もしかすると、そこまで知名度があるわけではないかもしれませんが、
イメージサイズとCVEの少なさは随一です。
高度なセキュリティが特徴です
Dockerのイメージサイズを縮小するのが趣味な人や、
セキュリティ大好きマンの方にはぜひとも試してほしいDockerイメージです。
Chainguardとは
そもそもChainguardとはなんぞやって話ですが、
Chainguard(イメージ)は、Chainguard社によって提供されている、
高いセキュリティを誇るコンテナイメージ群です。
dockerhubでChainguardと検索すると、
500以上のイメージがヒットすることがわかります。
Chainguardの特徴は、やはり従来のコンテナイメージの構築と運用に伴う
セキュリティリスクを軽減していることでしょう。
下記が公式のページです。
0 CVE containerと打ち出していることからも分かる通り、
セキュリティやコンテナイメージサイズの縮小などに強みを持った、
イメージであることがわかります。
ちなみに、CVEというのは、
Common Vulnerabilities and Exposures の略称で、共通脆弱性識別子と言ったりする、
要するに脆弱性のリストのようなものかと思います。
そんな脆弱性のリストが0ということからも分かる通り、
Chainguardが一般的なDockerイメージと比べて高いセキュリティを誇っていることが伺えます。
Chainguardのセキュリティ
すでに述べたようにChainguardの大きな魅力は、
そのセキュリティ機能です。
その中でも、Chainguardイメージは、
Wolfiというアンディストリビューション(undistro)なOSをベースとしていることが
大きな特徴でしょう。
このWolfiというのがまた結構面白いOSだなって思いまして、
これについてのドキュメントを読むだけでも普通に勉強になります
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を作成します
require 'sinatra'
set :bind, '0.0.0.0'
get '/' do
'Hello, from Docker!'
end
次にGemfileを作成します。
sinatraだけだとエラーになるので、rackupも追加します
source 'https://rubygems.org'
gem 'sinatra'
gem 'rackup'
次にDockerfile.rubyを作成します
FROM ruby:latest
WORKDIR /app
COPY Gemfile ./
RUN bundle install
COPY . .
CMD ["ruby", "app.rb"]
まあよくあるDockerファイルの記法という感じですね。
あとは、buildとrunをします。
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だけ作成します
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構成にします。
この辺の詳細はこのあたりに書かれています。
書きなれたDockerfileの記法とは少し異なる書き方が必要なので、
このあたりは慣れが必要かもしれません。
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イメージの環境構築
さて、3つ目にAlpineの方も行おうと思います。
Gemfileとかapp.rbは同じく共通なので、
Dockerfile.alpineをまず作成します
FROM ruby:alpine
WORKDIR /app
COPY Gemfile ./
RUN bundle install
COPY . .
CMD ["ruby", "app.rb"]
alpineの場合、何か別途インストールしないとダメかなと思いましたが、
意外にも普通のRubyイメージと同じ構成のDockerfileで問題なく立ち上がりました。
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でやれよっていうね。
各イメージの脆弱性スキャン
さて、各イメージの環境構築をしたので、
脆弱性スキャンやイメージサイズを比較してみます。
まず、イメージのサイズを比較してみます。
docker images | grep demo
ruby-demoが1.08GB、ruby-demo-alpineが170MB、
chainguardが51.8MBで、
chainguardが一番小さかったです
うん、ruby-latest大きすぎ
trivyを使った脆弱性スキャン
次に、trivyを使用して、イメージのセキュリティスキャンも実行してみます
trivyがなければ、homebrewとかでインストールしておきます。
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とほぼ同様だったので、
割愛します。
詳細はこちらをどうぞ
Docker Scoutでの比較
もうtrivyやgrypeを使って比較したので、
結果は見えていますが、
最後にdockerhubのScoutを使って見ようと思います。
結論ですが、これでやると、
alpineの脆弱性が5個ぐらいに増えました。
chainguardは相変わらず0(すげえなおい)
詳細はこちらをご覧ください
まとめ
有名どころのDockerイメージと
Chainguardイメージを主にイメージサイズと脆弱性の観点で
比較してみました。
Chainguardイメージは、やはりその高いセキュリティと
極限にまで縮小されたイメージサイズが特徴であり、
それらが大きな魅力だと私は感じました。
何気なく使っている、Dockerの普通のイメージとかが、
以下に肥大化したソフトウェアを内包していて、
多くの脆弱性を抱えているかがわかりました
Chainguardは、日本語での情報がまだ多くはなく、
通常のDockerfileに比べ、ややトリッキーな記法や、
Wolfi OSやdistroless、undistroといった、
人によっては馴染みが薄い概念も多くあるので、
技術的キャッチアップコストは、通常のDockerイメージよりは大きくなるかもしれませんが、
それを払ってでも、積極的に採用したい魅力的なDockerイメージだと思います。
それでは良い、Dockerイメージライフを