docker
dockerfile
kubernetes
docker-compose
静的解析

Dockerfileの静的解析ツールが便利すぎた

687474703a2f2f6861646f6c696e742e6c756b61736d617274696e656c6c692e63682f696d672f6361745f636f6e7461696e65722e706e67.png

背景

Dockerfileを静的解析したいと思い便利なツールがないかと探していたら発見しました。
DockerfileのLintツール

hadolint/hadolint

エラーが出るDockerfileをビルド前に検知してくれます。
ちなみにHaskellで書かれているようです。
現時点での最新バージョンはv1.15.0でmac等にも対応している。
GitHubのスター数は1000程である。

lintとは

wikiより引用

lint とは、主にC言語のソースコードに対し、コンパイラよりも詳細かつ厳密なチェックを行うプログラムである。
型の一致しない関数呼び出し
初期化されていない変数の参照
宣言されているが使われていない変数
同じ関数を参照しているが、戻り値を使う場合と使わない場合がある[疑問点 – ノート]
関数が戻り値を返す場合と返さない場合がある
など、コンパイラではチェックされないが、バグの原因になるような曖昧な記述についても警告される

導入手順

特に特別な手順はありません
バイナリをダウンロードして実行するだけで解析が始まります。

$ curl -L -O https://github.com/hadolint/hadolint/releases/download/v1.15.0/hadolint-Linux-x86_64
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   611    0   611    0     0    935      0 --:--:-- --:--:-- --:--:--   935
100 3561k  100 3561k    0     0   536k      0  0:00:06  0:00:06 --:--:--  767k

$ chmod +x hadolint-Linux-x86_64

実行

実行はバイナリを実行し引数にdockerfileを与えるのみで解析してくれます。

FROM debian:latest
MAINTAINER ryuichi1208

COPY package.json usr/src/app
RUN cd /usr/src/app \
    && npm install node-static

EXPOSE 80
EXPOSE 80000

上記のようなDockerfileを解析かけてみます

$ ./hadolint-Linux-x86_64 Dockerfile
Dockerfile:1 DL3007 Using latest is prone to errors if the image will ever update. Pin the version explicitly to a release tag
Dockerfile:2 DL4000 MAINTAINER is deprecated
Dockerfile:5 SC2164 Use 'cd ... || exit' or 'cd ... || return' in case cd fails.
Dockerfile:5 SC1109 This is an unquoted HTML entity. Replace with corresponding character.
Dockerfile:5 SC1070 Parsing stopped here. Mismatched keywords or invalid parentheses?
Dockerfile:5 DL3003 Use WORKDIR to switch to a directory
Dockerfile:9 DL3011 Valid UNIX ports range from 0 to 65535

はい結果は上記の通り。
ポートの範囲がおかしいよとか
現行では推奨しない書き方だったり
コンテナサイズについてまでコメントしてくれます。
もちろんエラーも出してくれます。

ルールの一覧はREADMEをご確認ください。

意図的にやってるんだよ!ってメッセージは除外できるような機能もありますのでそちらを。

$ hadolint --ignore DL3003 --ignore DL3006 <Dockerfile>

ちなみに指摘が一切ない場合は下記のように終了ステータスは0を返してくれる。
これをもとに何らかの自動化処理を入れるのもありですね。

$ ./hadolint-Linux-x86_64 Dockerfile ; echo $?
0

ルール集

GitHubより転載

ルール 説明
DL3000 絶対パスを使用してください
DL3001 For some bash commands it makes no sense running them in a Docker container like ssh, vim, shutdown, service, ps, free, top, kill, mount, ifconfig.
DL3002 Last user should not be root.
DL3003 WORKDIRを使用してディレクトリに切り替えます。
DL3004 Do not use sudo as it leads to unpredictable behavior. Use a tool like gosu to enforce root.
DL3005 apt-get upgradeまたはdist-upgradeを使用しないでください。
DL3007 Using latest is prone to errors if the image will ever update. Pin the version explicitly to a release tag.
DL3006 Always tag the version of an image explicitly.
DL3008 Pin versions in apt-get install.
DL3009 Delete the apt-get lists after installing something.
DL3010 アーカイブにイメージを抽出するには、ADDを使用してください
DL3011 Valid UNIX ports range from 0 to 65535.
DL3012 Provide an email address or URL as maintainer.
DL3013 Pin versions in pip.
DL3014 -yスイッチを使用します。
DL3015 Avoid additional packages by specifying --no-install-recommends.
DL3016 Pin versions in npm.
DL3017 apk upgradeを使用しないでください。
DL3018 Pin versions in apk add. Instead of apk add use apk add =.
DL3019 Use the --no-cache switch to avoid the need to use --update and remove /var/cache/apk/* when done installing packages.
DL3020 Use COPY instead of ADD for files and folders.
DL3021 COPY with more than 2 arguments requires the last argument to end with /
DL3022 COPY --from should reference a previously defined FROM alias
DL3023 COPY --from cannot reference its own FROM alias
DL3024 FROM aliases (stage names) must be unique
DL3025 Use arguments JSON notation for CMD and ENTRYPOINT arguments
DL3026 Use only an allowed registry in the FROM image
DL4000 MAINTAINERは推奨されていません。
DL4001 WgetまたはCurlを使用しますが、両方を使用することはできません。
DL4003 複数のCMD命令が見つかりました。
DL4004 Multiple ENTRYPOINT instructions found.
DL4005 Use SHELL to change the default shell.
DL4006 Set the SHELL option -o pipefail before RUN with a pipe in it
SC1000 $ is not used specially and should therefore be escaped.
SC1001 This \c will be a regular 'c' in this context.
SC1007 Remove space after = if trying to assign a value (or for empty string, use var='' ...).
SC1010 Use semicolon or linefeed before done (or quote to make it literal).
SC1018 This is a unicode non-breaking space. Delete it and retype as space.
SC1035 You need a space here
SC1045 It's not foo &; bar, just foo & bar.
SC1065 Trying to declare parameters? Don't. Use () and refer to params as $1, $2 etc.
SC1066 代入の左側に$を使用しないでください。
SC1068 Don't put spaces around the = in assignments.
SC1077 For command expansion, the tick should slant left (` vs ´).
SC1078 Did you forget to close this double-quoted string?
SC1079 This is actually an end quote, but due to next char, it looks suspect.
SC1081 Scripts are case sensitive. Use if, not If.
SC1083 This {/} is literal. Check expression (missing ;/\n?) or quote it.
SC1086 forループのイテレータ名に$を使用しないでください。
SC1087 Braces are required when expanding arrays, as in ${array[idx]}.
SC1095 You need a space or linefeed between the function name and body.
SC1097 Unexpected ==. For assignment, use =. For comparison, use [/[[.
SC1098 Quote/escape special characters when using eval, e.g. eval "a=(b)".
SC1099 #の前にスペースが必要です。
SC2026 This word is outside of quotes. Did you intend to 'nest '"'single quotes'"' instead'?
SC2028 echo won't expand escape sequences. Consider printf.
SC2035 Use ./glob or -- glob so names with dashes won't become options.
SC2046 Quote this to prevent word splitting
SC2086 Double quote to prevent globbing and word splitting.
SC2140 Word is in the form "A"B"C" (B indicated). Did you mean "ABC" or "A\"B\"C"?
SC2154 varは参照されますが割り当てられません。
SC2155 Declare and assign separately to avoid masking return values.

まとめ

いちいちbuildする手間も省けるので個人的には大活躍している。
アップデートは現在も続いており簡単なDockerfile作成時はパスを通したディレクトリへ置いておくことで
サクッとチェックできるのでお勧めです。