GitLab CIでTrivyを利用しDocker Imageの脆弱性を検知し、Slackに投稿する

tl;dr

  • Pipeline Schedule を利用し一定周期にscanを実行することで開発を止めずに脆弱性を検知する。
  • Slackに投稿することで脆弱性の検知をメンバーの目に入れることができる。

Trivyについて

昔書いたのでそちらを参照してください。 teitei-tk.hatenablog.com

コードを見せろ

Sample Repository

gitlab.com

Merge Request

gitlab.com

工夫したところ

gitlab-ci.ymlは特殊な事は書いておらず、ほとんどGitHubと、GitLabのサンプルコードをコピーしただけです。

工夫した点としては、Trivyの検知した結果のレポートをJUnitのフォーマットにしています。 GitHubのサンプルコードではGitLabのContainer scanningフォーマットで書かれていますが、Container ScanningはGold/Ultimate Planでないと利用できないため、JUnitフォーマットを利用しています。

今回はあえて、サンプルのため脆弱性のあるRack Gemなど、古いgemを利用しています。

JUnitフォーマットを利用した場合、MergeRequestの画面ではこのように表示されます。 f:id:teitei_tk:20201003212704p:plain

CVE-2019-16782などが表示されていることが確認できると思います。 リンクをクリックすると、CVEの詳細が表示されます。

f:id:teitei_tk:20201003213625p:plain

Schedule Pipelineを利用して一定周期でImageのScanを行う

新しくSchedule Pipelineを設定します。特に難しい点は無いと思うのでので設定は省略。 gitlab-ci.ymlの内容は下記です。特に変な点は書いていませんが、only: schedulesでSchedule Pipelineでのみ実行されるようにしています。

image_scan:
  image: docker:stable
  services:
    - name: docker:dind
  variables:
    DOCKER_HOST: tcp://docker:2375/
    DOCKER_DRIVER: overlay2
    # See https://github.com/docker-library/docker/pull/166
    DOCKER_TLS_CERTDIR: ""
    IMAGE: trivy-ci-test:$CI_COMMIT_SHA
  before_script:
    - export TRIVY_VERSION=$(wget -qO - "https://api.github.com/repos/aquasecurity/trivy/releases/latest" | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
    - echo $TRIVY_VERSION
    - wget --no-verbose https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz -O - | tar -zxvf -
  script:
    # Build image
    - docker build -t $IMAGE .
    # Fail on high and critical vulnerabilities
    - ./trivy --exit-code 1 --cache-dir .trivycache/ --severity CRITICAL,HIGH,MEDIUM --no-progress $IMAGE
  cache:
    paths:
      - .trivycache/
  only:
    - schedules

設定すると、CI/CDページにてSchduleと表示されていることが確認できると思います。

f:id:teitei_tk:20201003213848p:plain

SlackへScan結果を通知する。

ここも特に難しいことはないです。SlackにてApps -> Incoming WebHooksを追加。WebHook URLをコピーし、GitLabRepositoryのSettings -> CI/CD -> Slack notificationに移動。

Pipelineにチェックを入れ、通知するSlack Channelを記入するだけ。

その後、Schedule Pipelineを実行すると、このようにSlackに通知されます。

f:id:teitei_tk:20201003214742p:plain

Trivy を利用した Docker Container・Image の脆弱性検知入門

tl;dr

  • trivyを利用することで簡単にDocker コンテナの脆弱性の検知をすることが出来る。
    • OSライブラリからアプリケーション依存のライブラリまで検知が可能
  • SimpleかつREADMEが豊富で導入の敷居が低い。
  • CIに組み込むことで DevOps から DevSecOps への移行を加速することが可能。

コンテナの脆弱性

2020/08/06 現在、Dockerを利用するしてプロダクトの環境を作る事がデファクトスタンダードになっている。 どの会社に行ってもセキュリティは最重要視されていると言っても過言ではないが、Docker イメージを生成 or 取得し、そのまま利用を続け、時間が経ち、利用しているライブラリに不具合が生じているのにも関わらず、知らないうちに脆弱性があるContainerを利用しているケースは少なくない。

脆弱性を放置して起きたインシデントは具体的に下記のような事例がある。

コンテナに仮想通貨の採掘を行うマルウェアイメージをダウンロードさせるキャンペーンが頻繁に行われており、ピーク時は 2,000以上の Dockerホストがマルウェア感染したとの報告もあります。

www.netone.co.jp

脆弱性の検知

コンテナの脆弱性検知を行うツールは複数ある。

Docker Bench Security

github.com

clair

github.com

anchore

anchore.com

今回はOSSのTrivyを利用する。

Trivyとは

AquaSectiry社が提供するOSS。特徴として、A Simple and Comprehensive Vulnerability Scanner for Containers, Suitable for CI と書かれているように非常にシンプルかつ、とても簡単にCIに組み込むことが出来る。

github.com

製作者は日本の方で、OSSで作成したものをAquaSecutiry社が買収し、そのまま就職をして仕事でOSS開発をするというエンジニアには夢のような記事を書かれていたことを覚えている。

knqyf263.hatenablog.com

READMEがとても豊富にかかれており、各プラットフォームでのインストールの方法が書かれており、気軽に手元で実践することが出来ることも非常に魅力的。

https://github.com/aquasecurity/trivy#installation

特に利用していきたいと決めた点は、OSパッケージの脆弱性だけではなく、各アプリケーションの依存ライブラリの脆弱性の検知も出来る。 アプリケーションの依存ライブラリというのは、RubyでいうGemfile.lock、NodeJSでいうpackage-lock.json, yarn.lock。 メジャーなアプリケーションの依存ライブラリはサポートされている印象で、業務でも使いやすい。

https://github.com/aquasecurity/trivy#application-dependencies

Trivyで脆弱性を検知する

実際に検知をしてみる。今回は3年前に作ったきり更新していないDocker Imageを利用する。

github.com

hub.docker.com

$ trivy image docker.io/teitei/docker-chrome-stable:latest
2020-08-07T00:30:14.481+0900  [33mWARN[0m You should avoid using the :latest tag as it is cached. You need to specify '--clear-cache' option when :latest image is changed
2020-08-07T00:30:46.674+0900  [34mINFO[0m Detecting Ubuntu vulnerabilities...

docker.io/teitei/docker-chrome-stable:latest (ubuntu 16.04)
===========================================================
Total: 930 (UNKNOWN: 0, LOW: 462, MEDIUM: 446, HIGH: 22, CRITICAL: 0)

+--------------------------+------------------+----------+-------------------------------------+---------------------------------------+----------------------------------------------------------+
|         LIBRARY          | VULNERABILITY ID | SEVERITY |          INSTALLED VERSION          |             FIXED VERSION             |                          TITLE                           |
+--------------------------+------------------+----------+-------------------------------------+---------------------------------------+----------------------------------------------------------+
| apt                      | CVE-2019-3462    | HIGH     | 1.2.20                              | 1.2.29ubuntu0.1                       | Incorrect sanitation of the                              |
|                          |                  |          |                                     |                                       | 302 redirect field in HTTP                               |
|                          |                  |          |                                     |                                       | transport method of...                                   |
+                          +------------------+----------+                                     +---------------------------------------+----------------------------------------------------------+
|                          | CVE-2020-3810    | MEDIUM   |                                     | 1.2.32ubuntu0.1                       | Missing input validation in                              |
|                          |                  |          |                                     |                                       | the ar/tar implementations of                            |
|                          |                  |          |                                     |                                       | APT before version 2.1.2...                              |
+--------------------------+------------------+----------+-------------------------------------+---------------------------------------+----------------------------------------------------------+
| bash                     | CVE-2019-18276   | LOW      | 4.3-14ubuntu1.2                     |                                       | bash: when effective UID is                              |
|                          |                  |          |                                     |                                       | not equal to its real UID                                |
|                          |                  |          |                                     |                                       | the...                                                   |
+                          +------------------+          +                                     +---------------------------------------+----------------------------------------------------------+
|                          | CVE-2019-9924    |          |                                     | 4.3-14ubuntu1.4                       | bash: BASH_CMD is writable in                            |
|                          |                  |          |                                     |                                       | restricted bash shells                                   |
+--------------------------+------------------+          +-------------------------------------+---------------------------------------+----------------------------------------------------------+
| bsdutils                 | CVE-2016-2779    |          | 2.27.1-6ubuntu3.2                   |                                       | util-linux: runuser tty hijack                           |
|                          |                  |          |                                     |                                       | via TIOCSTI ioctl                                        |
+                          +------------------+          +                                     +---------------------------------------+----------------------------------------------------------+
|                          | CVE-2016-5011    |          |                                     |                                       | util-linux: Extended partition                           |
|                          |                  |          |                                     |                                       | loop in MBR partition table                              |
|                          |                  |          |                                     |                                       | leads to DOS                                             |
+--------------------------+------------------+          +-------------------------------------+---------------------------------------+----------------------------------------------------------+
| coreutils                | CVE-2016-2781    |          | 8.25-2ubuntu2                       |                                       | coreutils: Non-privileged                                |
|                          |                  |          |                                     |                                       | session can escape to the                                |
|                          |                  |          |                                     |                                       | parent session in chroot                                 |
+--------------------------+------------------+----------+-------------------------------------+---------------------------------------+----------------------------------------------------------+
....
....
....

Total: 930 (UNKNOWN: 0, LOW: 462, MEDIUM: 446, HIGH: 22, CRITICAL: 0) と画面に収まりきれないほど検知出来ている。

アプリケーション依存のライブラリに存在する脆弱性の検出も出来る。これは私が雑に作ったものを入れているRepositoryで、Sinatraを利用しているが、rackに脆弱性がある。

github.com

$ trivy image teitei-tk/zatsu/ruby-rsa
2020-08-07T00:21:22.325+0900    WARN    You should avoid using the :latest tag as it is cached. You need to specify '--clear-cache' option when :latest image is changed
2020-08-07T00:21:28.282+0900    INFO    Detecting Alpine vulnerabilities...
2020-08-07T00:21:28.290+0900    INFO    Detecting bundler vulnerabilities...

teitei-tk/zatsu/ruby-rsa (alpine 3.12.0)
========================================
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)


app/Gemfile.lock
================
Total: 3 (UNKNOWN: 0, LOW: 0, MEDIUM: 3, HIGH: 0, CRITICAL: 0)

+---------+------------------+----------+-------------------+---------------+--------------------------------+
| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION |             TITLE              |
+---------+------------------+----------+-------------------+---------------+--------------------------------+
| rack    | CVE-2019-16782   | MEDIUM   | 2.0.7             | 2.0.8, 1.6.12 | rubygem-rack: hijack sessions  |
|         |                  |          |                   |               | by using timing attacks        |
|         |                  |          |                   |               | targeting the session id       |
+         +------------------+          +                   +---------------+--------------------------------+
|         | CVE-2020-8161    |          |                   | 2.1.3         | rubygem-rack: directory        |
|         |                  |          |                   |               | traversal in Rack::Directory   |
+         +------------------+          +                   +---------------+--------------------------------+
|         | CVE-2020-8184    |          |                   | 2.2.3, 2.1.4  | rubygem-rack: percent-encoded  |
|         |                  |          |                   |               | cookies can be used to         |
|         |                  |          |                   |               | overwrite existing prefixed    |
|         |                  |          |                   |               | cookie names...                |
+---------+------------------+----------+-------------------+---------------+--------------------------------+
....
....
....

Rackに脆弱性があることがわかる。

フィルタリング

サーベイ

CRITICAL, HIGH など、サーベイによってフィルタリングも出来る。

$ trivy image --severity CRITICAL,HIGH docker.io/teitei/docker-chrome-stable:latest
2020-08-07T00:32:37.159+0900    WARN    You should avoid using the :latest tag as it is cached. You need to specify '--clear-cache' option when :latest image is changed
2020-08-07T00:32:37.216+0900    INFO    Detecting Ubuntu vulnerabilities...

docker.io/teitei/docker-chrome-stable:latest (ubuntu 16.04)
===========================================================
Total: 22 (HIGH: 22, CRITICAL: 0)

+--------------------+------------------+----------+-------------------+-------------------+--------------------------------+
|      LIBRARY       | VULNERABILITY ID | SEVERITY | INSTALLED VERSION |   FIXED VERSION   |             TITLE              |
+--------------------+------------------+----------+-------------------+-------------------+--------------------------------+
| apt                | CVE-2019-3462    | HIGH     | 1.2.20            | 1.2.29ubuntu0.1   | Incorrect sanitation of the    |
|                    |                  |          |                   |                   | 302 redirect field in HTTP     |
|                    |                  |          |                   |                   | transport method of...         |
+--------------------+                  +          +                   +                   +                                +
| libapt-pkg5.0      |                  |          |                   |                   |                                |
|                    |                  |          |                   |                   |                                |
|                    |                  |          |                   |                   |                                |
+--------------------+------------------+          +-------------------+-------------------+--------------------------------+
| libc-bin           | CVE-2018-1000001 |          | 2.23-0ubuntu7     | 2.23-0ubuntu10    | glibc: realpath() buffer       |
|                    |                  |          |                   |                   | underflow when getcwd()        |
|                    |                  |          |                   |                   | returns relative path allows   |
|                    |                  |          |                   |                   | privilege escalation...        |
+--------------------+                  +          +                   +                   +                                +
| libc6              |                  |          |                   |                   |                                |
|                    |                  |          |                   |                   |                                |
|                    |                  |          |                   |                   |                                |
|                    |                  |          |                   |                   |                                |
+
....
....
....

OS or Library

表示する脆弱性の種類をOS or アプリケーション依存ライブラリを選ぶことも出来る

$ trivy image --vuln-type os  --s CRITICAL,HIGH docker.io/teitei/docker-chrome-stable:latest
2020-08-07T00:33:44.006+0900    WARN    You should avoid using the :latest tag as it is cached. You need to specify '--clear-cache' option when :latest image is changed
2020-08-07T00:33:44.061+0900    INFO    Detecting Ubuntu vulnerabilities...

docker.io/teitei/docker-chrome-stable:latest (ubuntu 16.04)
===========================================================
Total: 22 (HIGH: 22)

+--------------------+------------------+----------+-------------------+-------------------+--------------------------------+
|      LIBRARY       | VULNERABILITY ID | SEVERITY | INSTALLED VERSION |   FIXED VERSION   |             TITLE              |
+--------------------+------------------+----------+-------------------+-------------------+--------------------------------+
| apt                | CVE-2019-3462    | HIGH     | 1.2.20            | 1.2.29ubuntu0.1   | Incorrect sanitation of the    |
|                    |                  |          |                   |                   | 302 redirect field in HTTP     |
|                    |                  |          |                   |                   | transport method of...         |
+--------------------+                  +          +                   +                   +                                +
| libapt-pkg5.0      |                  |          |                   |                   |                                |
|                    |                  |          |                   |                   |                                |
|                    |                  |          |                   |                   |                                |
+--------------------+------------------+          +-------------------+-------------------+--------------------------------+
| libc-bin           | CVE-2018-1000001 |          | 2.23-0ubuntu7     | 2.23-0ubuntu10    | glibc: realpath() buffer       |
|                    |                  |          |                   |                   | underflow when getcwd()        |
|                    |                  |          |                   |                   | returns relative path allows   |
|                    |                  |          |                   |                   | privilege escalation...        |
+--------------------+                  +          +                   +                   +                                +
| libc6              |                  |          |                   |                   |                                |
|                    |                  |          |                   |                   |                                |
|                    |                  |          |                   |                   |                                |
|                    |                  |          |                   |                   |                                |
+
....
....
....

脆弱性の修正方法

Docker ImageをBuildをし直すだけ。

$ docker build -t teitei/docker-chrome-stable:new .
$ trivy image teitei/docker-chrome-stable:new
2020-08-07T00:39:12.168+0900    INFO    Detecting Ubuntu vulnerabilities...

teitei/docker-chrome-stable:new (ubuntu 16.04)
==============================================
Total: 275 (UNKNOWN: 0, LOW: 244, MEDIUM: 31, HIGH: 0, CRITICAL: 0)

+-----------------------+------------------+----------+---------------------------------------+---------------+------------------------------------------------+
|        LIBRARY        | VULNERABILITY ID | SEVERITY |           INSTALLED VERSION           | FIXED VERSION |                     TITLE                      |
+-----------------------+------------------+----------+---------------------------------------+---------------+------------------------------------------------+
| bash                  | CVE-2019-18276   | LOW      | 4.3-14ubuntu1.4                       |               | bash: when effective UID is                    |
|                       |                  |          |                                       |               | not equal to its real UID                      |
|                       |                  |          |                                       |               | the...                                         |
+-----------------------+------------------+          +---------------------------------------+---------------+------------------------------------------------
....
....
....

これはDockerfileのBaseImageに ubuntu:16.04 と古いImageを利用しているためLOWとMEDIUMが消えていないが、先程に比べてHIGHが0件になり、MEDIUMの検知数が415件減っていることがわかる。

CIへの組み込み

参考PR

github.com

今回はGitHubActionを利用してtrivyを実行している。trivyはtrivy-actionというGitHubActionに組み込むことが出来るActionを公開しているので、そちらを利用した。

github.com

更にGitHubActionはSchedule機能があるので、一定間隔にチェックすることも出来る。

docs.github.com

その他のCI

CircleCIやTravisCIを使っている場合でもREADMEにサンプルがあるので、それを参考にすることですぐに組み込むことが出来る。

まとめ

  1. コンテナを作成してそのまま放置し続けるのは脆弱性を放置することになり危険。
  2. trivyを利用することで簡単にコンテナの脆弱性を検知することが出来る。
  3. trivyはREADMEも豊富に書かれているのと、SimpleでCIに組み込みやすいので、比較的導入がしやすい。
  4. OSのライブラリだけでなく、アプリケーション依存のライブラリの脆弱性検知も出来る。

ref

github.com

www.slideshare.net

qiita.com

GitHubのProfileページにREADMEを追加した。

tl;dr

  • GitHubのProfileページに自分のREADMEを載せることが出来るようになった。

f:id:teitei_tk:20200801030736p:plain

いつからか分からないが、このようにGitHubのProfileページに自分のREADMEを載せることが出来るようになっている。 いくらか調べてみたが、どれも一次ソースが無いので何時から始まったからは分からなかった。

dev.to

やり方は簡単でGitHub上で、下記の手順で出来る。

  1. 自分のGitHubのアカウント名と同じ名前のRepositoryを作る
  2. RepositoryにREADME.mdを編集してpushする。

私の場合はteitei-tkというアカウント名なので、teitei-tkというrepositoryを作った。 github.com

他の人がどんなREADMEを書いているのか知りたい場合や、どんなツールがあるのかはawsome-github-profile-readmeというRepositoryによくまとまっているので、そこを見ると良い github.com

結構いろんなことが出来て、自分のGitHubのstatsを表示したり、GitHubアカウントで一番利用している言語を表示することが出来たりする。

f:id:teitei_tk:20200731220449p:plain f:id:teitei_tk:20200731221317p:plain

その他にもShields.ioとSimpleIconsを利用してカスタムBadgeをつけれたりもする shields.io simpleicons.org

これは試しに作ったRubyRailsのカスタムBadge

Ruby RubyOnRails

ソースコードはこんな感じ

![Ruby](https://img.shields.io/badge/-Ruby-CC342D?style=flat-square&logo=Ruby)
![RubyOnRails](https://img.shields.io/badge/-Ruby%20on%20Rails-CC0000?style=flat-square&logo=Ruby+on+Rails)

具体的にはこのように書く。

![](https://img.shields.io/badge/-(message)-(badgeの色。16進数でも可能)?style=(表示するstyle)&logo=(simpleIcons.orgにあるlogoの名前)&logoColor=(logoの色)

StatsとBadgeの詳しいことはQiitaに良いまとめ記事があるのでそこに譲る。

qiita.com

qiita.com

個人的な感想

LaprasやFindyのActivityLogやスキルまとめなどと相性が良さそうなので、Lapras社とFindy社はGitHub Profileに載せることが出来るBadgeを作って欲しい。

まとめ

  • 自分のGitHubアカウントのトップページにREADMEを載せることが出来るようになった。
  • GitHub上のStatsやBadgeを載せたりカスタマイズが出来る。

ref

dev.to

github.com

qiita.com

qiita.com

Next.jsの開発環境をDocker with Multi Stage Build

tl;dr

  • Multi Stage Buildを利用することで147MBほど減った。
  • 今回は開発環境のDocker化が目的。production環境ではSSG or SSRだと思うのでまた別の対応が必要になりそう。

Example Project

github.com

完成形

gist.github.com

Multi Stage Buildを利用しないケース

$ docker build -f Dockerfile_dev .
Sending build context to Docker daemon  139.6MB
Step 1/9 : FROM node:12.18.2-alpine as builder
 ---> 057fa4cc38c2
Step 2/9 : WORKDIR /app
 ---> Running in f53a1b123569
Removing intermediate container f53a1b123569
 ---> 485a44f0ebd3
Step 3/9 : COPY package.json .
 ---> 5b58fb396840
Step 4/9 : COPY yarn.lock .
 ---> 8fbd886ebe49
Step 5/9 : COPY tsconfig.json .
 ---> dd9ea9f86d64
Step 6/9 : RUN yarn install --production
 ---> Running in 997d0a1b080d
yarn install v1.22.4
[1/4] Resolving packages...
[2/4] Fetching packages...
info fsevents@1.2.13: The platform "linux" is incompatible with this module.
info "fsevents@1.2.13" is an optional dependency and failed compatibility check. Excluding it from installation.
info fsevents@2.1.3: The platform "linux" is incompatible with this module.
info "fsevents@2.1.3" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
[4/4] Building fresh packages...
Done in 39.21s.
Removing intermediate container 997d0a1b080d
 ---> df1be46b2ed3
Step 7/9 : COPY . .
 ---> d91d29256d71
Step 8/9 : EXPOSE 3000
 ---> Running in f6db73858185
Removing intermediate container f6db73858185
 ---> ef8b3ac6a59f
Step 9/9 : CMD [ "yarn", "dev" ]
 ---> Running in 003480d5135e
Removing intermediate container 003480d5135e
 ---> f9590e7cb54e
Successfully built f9590e7cb54e
$ docker image ls | grep f9590e7cb54e
<none>                             <none>              f9590e7cb54e        16 seconds ago      431MB

431MB

Multi Stage Buildを利用するケース

$ docker build -f Dockerfile_dev .
Sending build context to Docker daemon  139.6MB
Step 1/11 : FROM node:12.18.2-alpine as builder
 ---> 057fa4cc38c2
Step 2/11 : WORKDIR /app
 ---> Using cache
 ---> 485a44f0ebd3
Step 3/11 : COPY package.json .
 ---> Using cache
 ---> 5b58fb396840
Step 4/11 : COPY yarn.lock .
 ---> Using cache
 ---> 8fbd886ebe49
Step 5/11 : COPY tsconfig.json .
 ---> Using cache
 ---> dd9ea9f86d64
Step 6/11 : RUN yarn install --production
 ---> Using cache
 ---> df1be46b2ed3
Step 7/11 : FROM node:12.18.2-alpine
 ---> 057fa4cc38c2
Step 8/11 : COPY --from=builder /app/node_modules /app/node_modules
 ---> fd4d5616ed40
Step 9/11 : COPY . .
 ---> 8964cefa6942
Step 10/11 : EXPOSE 3000
 ---> Running in 16526606b919
Removing intermediate container 16526606b919
 ---> e5a03c34f700
Step 11/11 : CMD [ "yarn", "dev" ]
 ---> Running in a74809ecc5d4
Removing intermediate container a74809ecc5d4
 ---> 5ca7bf661d87
Successfully built 5ca7bf661d87
$ docker image ls | grep 5ca7bf661d87
<none>                             <none>              5ca7bf661d87        28 seconds ago      284MB

まとめ

  • Multi Stage Buildを利用することでイメージの容量がだいぶ減るので利用しない手はない。

ref

tech.plaid.co.jp docs.docker.com

Docker with GoのMulti Stage Buildで"no such file or directory"と出てハマった話

対象のDockerfile。特に特殊なことは行っていない素朴なDockerfile。

FROM golang:1.14.4 as builder

WORKDIR /app
COPY . .
RUN go mod download
RUN go build path/to/main.go

FROM alpine
RUN apk add --no-cache ca-certificates
COPY --from=builder /app/path/to/main /app/main
EXPOSE 5000
ENTRYPOINT [ "/app/main" ]

build log

$ docker build .
Sending build context to Docker daemon  24.16MB
Step 1/10 : FROM golang:1.14.4 as builder
 ---> 00d970a31ef2
Step 2/10 : WORKDIR /app
 ---> Using cache
 ---> 290d933fcd92
Step 3/10 : COPY . .
 ---> 5c338d20e119
Step 4/10 : RUN go mod download
 ---> Running in fa40233a9ad4
Removing intermediate container fa40233a9ad4
 ---> 32ededdd590c
Step 5/10 : RUN go build cmd/app/main.go
 ---> Running in 715374ae5931
Removing intermediate container 715374ae5931
 ---> 0df4e6ac142b
Step 6/10 : FROM alpine
 ---> a24bb4013296
Step 7/10 : RUN apk add --no-cache ca-certificates
 ---> Using cache
 ---> 63a365fab492
Step 8/10 : COPY --from=builder /app/main /app/main
 ---> 262cef1fcd0f
Step 9/10 : EXPOSE 5000
 ---> Running in d2cc58f47b7f
Removing intermediate container d2cc58f47b7f
 ---> 49e6a57ba3bd
Step 10/10 : ENTRYPOINT [ "/app/main" ]
 ---> Running in 887cc0dbb3a8
Removing intermediate container 887cc0dbb3a8
 ---> d5c6b638a8a7
Successfully built d5c6b638a8a7

buildしたimageを実行すると exec user process caused "no such file or directory" と出て実行が出来ない。

$ docker run -it d5c6b638a8a7
standard_init_linux.go:211: exec user process caused "no such file or directory"

Docker公式のDocumentを眺めると CGO_ENABLED=0 という変数を指定している。

docs.docker.com

CGO_ENABLEについて公式のDocumentに書いてある。

golang.org

The cgo tool is enabled by default for native builds on systems where it is expected to work. It is disabled by default when cross-compiling. > You can control this by setting the CGO_ENABLED environment variable when running the go tool: set it to 1 to enable the use of cgo, and to > 0 to disable it. The go tool will set the build constraint "cgo" if cgo is enabled. The special import "C" implies the "cgo" build constraint, as > > though the file also said "// +build cgo". Therefore, if cgo is disabled, files that import "C" will not be built by the go tool. (For more about >build constraints see https://golang.org/pkg/go/build/#hdr-Build_Constraints).

今回のMulti Stage Buildのようなビルド環境と実行環境が違うクロスコンパイル時には CGO_ENABLE=0 にする必要があるとのこと。go envで見てみると案の定 CGO_ENABLE=1 となっている。

Step 6/11 : RUN go env
 ---> Running in f2d4bf833350
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/root/.cache/go-build"
GOENV="/root/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/app/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build835780014=/tmp/go-build -gno-record-gcc-switches"

Dockerfileのbuild箇所に CGO_ENABLE=0 を指定することで正常にビルドが出来ました。

FROM golang:1.14.4 as builder

WORKDIR /app
COPY . .
RUN go mod download
RUN CGO_ENABLED=0 go build path/to/main.go
RUN go env

FROM alpine
RUN apk add --no-cache ca-certificates
COPY --from=builder /app/path/to/main /app/main
EXPOSE 5000
ENTRYPOINT [ "/app/main" ]
$ docker build .
Sending build context to Docker daemon  24.16MB
Step 1/11 : FROM golang:1.14.4 as builder
 ---> 00d970a31ef2
Step 2/11 : WORKDIR /app
 ---> Using cache
 ---> 290d933fcd92
Step 3/11 : COPY . .
 ---> 1e985aa7595b
Step 4/11 : RUN go mod download
 ---> Running in 4f47920a2f07
Removing intermediate container 4f47920a2f07
 ---> b3af89cc0fae
Step 5/11 : RUN CGO_ENABLED=0 go build cmd/app/main.go
 ---> Running in 5418122649bb
Removing intermediate container 5418122649bb
 ---> a22ffab8bc7c
Step 6/11 : RUN go env
 ---> Running in f2d4bf833350
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/root/.cache/go-build"
GOENV="/root/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/app/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build835780014=/tmp/go-build -gno-record-gcc-switches"
Removing intermediate container f2d4bf833350
 ---> 658a897f518b
Step 7/11 : FROM alpine
 ---> a24bb4013296
Step 8/11 : RUN apk add --no-cache ca-certificates
 ---> Using cache
 ---> 63a365fab492
Step 9/11 : COPY --from=builder /app/main /app/main
 ---> 4aea7dfd10cd
Step 10/11 : EXPOSE 5000
 ---> Running in 17d50b507678
Removing intermediate container 17d50b507678
 ---> e35b39616e48
Step 11/11 : ENTRYPOINT [ "/app/main" ]
 ---> Running in 2a03dd0d5636
Removing intermediate container 2a03dd0d5636
 ---> 7f1b74a479da
Successfully built 7f1b74a479da
$ docker run -it 7f1b74a479da

   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.1.16
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on 127.0.0.1:5000

ref

qiita.com docs.docker.com golang.org github.com