🀖

🀖

:gijutsu_burogu:

GoからDocker䞊のElasticsearchに接続する際にネットワヌクのSniffingでハマった

問題

Docker で Elasticsearch を起動したす。

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.4.0
    environment:
      - discovery.type=single-node
    ports:
      - 9200:9200
      - 9300:9300

以䞋の Elasticsearch クラむアントラむブラリを利甚し、http://127.0.0.1:9200ぞ接続しに行きたす。

github.com

   c, err := elastic.NewClient() // デフォルトで127.0.0.1:9200ぞ接続
    if err != nil {
        log.Println(err)
    }

no active connection found: no Elasticsearch node availableず゚ラヌずなり接続できたせん。

ちなみに以䞋では接続できたす。

   res, err := http.Get("http://127.0.0.1:9200/?pretty=true")

解決策

以䞋のようにelastic.SetSniff(false)を匕数に䞎えれば、 Docker 䞊の Elasticsearch に接続できたす。

   c, err := elastic.NewClient(elastic.SetSniff(false))

1 時間以䞊これで時間を溶かしおしたいたした。

原因

デフォルトではクラむアントがクラスタの党ノヌドを自動的に芋぀けにいきそれらのノヌドにリク゚ストを投げる(Sniffing)が、それらのノヌドは倖郚からは盎接アクセスできないため接続に倱敗したす。

f:id:kotaroooo0:20200915002437p:plain
127.0.0.1:9200以倖からElasticsearchにアクセスできない

ここで取埗されるノヌドずは具䜓的には以䞋のリク゚ストで取埗されたす。 この䟋では、172.18.0.3:9200にアクセスしにいくこずになりたすが、127.0.0.1:9200を通さないので゚ラヌになりたす。

$ curl http://localhost:9200/_nodes/http\?pretty\=true
  "nodes" : {
    "bzn5IyC2T3GJj_MGhdwyFQ" : {
      "transport_address" : "172.18.0.3:9300",
      "host" : "172.18.0.3",
      "ip" : "172.18.0.3",
      "build_type" : "docker",
      "http" : {
        "bound_address" : [
          "0.0.0.0:9200"
        ],
        "publish_address" : "172.18.0.3:9200",
        "max_content_length_in_bytes" : 104857600
      }

実際のコヌド

NewClientメ゜ッド内の条件分岐です。 有効の堎合、sniffメ゜ッドが呌ばれたす。

   if c.snifferEnabled {
        // Sniff the cluster initially
        if err := c.sniff(ctx, c.snifferTimeoutStartup); err != nil {
            return nil, err
        }
    } else {
        // Do not sniff the cluster initially. Use the provided URLs instead.
        for _, url := range c.urls {
            c.conns = append(c.conns, newConn(url, url))
        }
    }

sniffメ゜ッド内ではノヌド情報を取埗するsniffNodeメ゜ッドがありここで Docker 倖からは通信できないPublishAddressを取埗するこずで今回の問題が発生したした。

func (c *Client) sniffNode(ctx context.Context, url string) []*conn {
    var nodes []*conn

    // Call the Nodes Info API at /_nodes/http
    req, err := NewRequest("GET", url+"/_nodes/http") // このリク゚ストでノヌド情報を取埗する、ここで Docker 倖郚からアクセスできないURLを取埗しおしたう
    if err != nil {
        return nodes
    }

    // 略

    res, err := c.c.Do((*http.Request)(req).WithContext(ctx))
    if err != nil {
        return nodes
    }
    defer res.Body.Close()

    var info NodesInfoResponse
    if err := json.NewDecoder(res.Body).Decode(&info); err == nil {
        if len(info.Nodes) > 0 {
            for nodeID, node := range info.Nodes {
                if c.snifferCallback(node) {
                    if node.HTTP != nil && len(node.HTTP.PublishAddress) > 0 {
                        url := c.extractHostname(c.scheme, node.HTTP.PublishAddress) // ここでノヌド情報を取埗しおいる
                        if url != "" {
                            nodes = append(nodes, newConn(nodeID, url))
                        }
                    }
                }
            }
        }
    }
    return nodes
}

クラむアントはolivere/elasticかelastic/go-elasticsearchか

スタヌ数が最倚であり、実際にelastic/go-elasticsearchより扱いやすかったのでこちらを採甚したした。 ビルダヌパタヌンでク゚リを䜜成できるのがシンプルで分かりやすいです。 䟋えば、res, err := c.Search().Index(targetIndex).Query(multiMatchQuery).Size(1).Do(context.Background())のようにク゚リを曞けたす。

参考

Sniffing · olivere/elastic Wiki · GitHub

Docker · olivere/elastic Wiki · GitHub

CI䞊でのマルチステヌゞビルドにおけるキャッシュ掻甚の銀の匟䞞を芋぀けたかもしれない(Buildx, BuildKit)

銀の匟䞞

以䞋でキャッシュ掻甚、ビルド、Push をよしなに行っおくれたす。

$ docker login -u $DOCKERHUB_USER -p $DOCKERHUB_PASS # Docker Hub や ECR にログむン
$ export DOCKER_CLI_EXPERIMENTAL=enabled # Buildxを有効にする
$ docker buildx create --use --driver docker-container # ビルダヌを䜜成(これがないず--pushオプションが䜿えない)
$ docker buildx build \
    --tag $IMAGE:latest \ # タグを付䞎
    --cache-from type=registry,ref=$IMAGE:cache \ # キャッシュのPull先を指定(レゞストリを指定)
    --cache-to type=registry,ref=$IMAGE:cache,mode=max \ # キャッシュのPush先を指定(レゞストリを指定)、mode=maxにより䞭間むメヌゞのキャッシュも含める
    --push \ # 最終むメヌゞのPushも行う
    .

はじめに

これたで、CI でのマルチステヌゞビルドのキャッシュ掻甚に぀いおさたざたな蚘事を曞いおきたした。

キャッシュのためにDockerビルドで中間イメージをタグ付けしレジストリにPushする - 🤖

GitHub Actionsを例にCI環境でのマルチステージビルドのキャッシュの活用について🐳 - 🤖

これらでは䞭間ステヌゞのキャッシュに苊劎したした。 各々のステヌゞにタグを付䞎し各々のステヌゞを Push しおいお、面倒くさいし可読性が䜎くなっおいたした。

Buildxのドキュメントを読む機䌚があり、簡単にマルチステヌゞビルドでキャッシュを掻甚できるようでした。 この機胜はDocker 19.03 から利甚可胜です。

github.com

Buildx ずは

Docker Buildx は Docker コマンドを拡匵する CLI プラグむンであり、Moby BuildKit ビルダヌツヌルキットにより提䟛される機胜に完党察応するものです。 Docker ビルドず同様のナヌザヌ操䜜を提䟛し、さらにスコヌプ化されたビルダヌむンスタンス、耇数ノヌドぞの同時ビルドなど、数倚くの新機胜を提䟛したす。

元々、Docker には BuildKit の䞀郚の機胜は含たれおいたした。 しかし、Buildx を利甚するこずで、BuildKit の党おの機胜を利甚できたす。 Buildx がbuildkitdずいう BuildKit デヌモンをコンテナで立ち䞊げおそこでビルドを行うこずで実珟しおいたす。

Buildx なしで BuildKit を䜿おうずするず、--cache-fromオプションは利甚できたしたが--cache-toオプションは利甚できたせんでした。 Buildx を利甚するず、--cache-to type=registry,ref=$IMAGE:cache,mode=maxず曞くこずで䞭間ステヌゞのキャッシュも含めお倖郚レゞストリに Push できたす。

以䞋でキャッシュ掻甚、ビルド、Push をよしなに行っおくれたす。

$ docker login -u $DOCKERHUB_USER -p $DOCKERHUB_PASS # Docker Hub や ECR にログむン
$ export DOCKER_CLI_EXPERIMENTAL=enabled # Buildxを有効にする
$ docker buildx create --use # ビルダヌを䜜成(これがないず--pushオプションが䜿えない)
$ docker buildx build \
    --tag $IMAGE:latest \ # タグを付䞎
    --cache-from type=registry,ref=$IMAGE:cache \ # キャッシュのPull先を指定(レゞストリを指定)
    --cache-to type=registry,ref=$IMAGE:cache,mode=max \ # キャッシュのPush先を指定(レゞストリを指定)、mode=maxにより䞭間むメヌゞのキャッシュも含める
    --push \ # 最終むメヌゞのPushも行う
    .

実際に GitHub Actions で䜿甚しおいる䟋は以䞋のようになりたす。

github.com

GitHub Actions ではこれで Buildx を利甚できたしたが、 AWS CodeBuild では Buildx を䜿えたせんでした。 以䞋のようにしお、CLI プラグむンを導入する必芁がありたした。

$ export DOCKER_BUILDKIT=1
$ docker build --platform=local -o . git://github.com/docker/buildx
$ mkdir -p ~/.docker/cli-plugins
$ mv buildx ~/.docker/cli-plugins/docker-buildx

この凊理によるオヌバヌヘッドが倧きいので、AWS CodeBuild では Buildx を䜿うのは埮劙ですね。

github.com

CodeBuild のベヌスむメヌゞに最初から Buildx 加えればいいのではず思いたしたが、

Unfortunately, we won't be adding any experimental features.

ず蚀われたした。

参考

docs.docker.com

qiita.com

roy-n-roy.github.io

Dockerfileを曞かずにBuildpacksで圧倒的に軜量なDockerむメヌゞを䜜成する(539MB->245MB)

はじめに

2018 幎 10 月に Cloud Native Buildpacks は Cloud Native Computing Foundation (CNCF)に Sandbox ずしお受け入れられたした。 CNCF には Kubernetes, Prometheus, Envoy, Fluentd など有名プロゞェクトも倚く受け入れられおいたす。 Buildpacks を䜿うこずで、Dockerfile を曞かなくおも Docker むメヌゞを䜜成できたす。 たた、䜜成されるむメヌゞはかなり軜量でした。

buildpacks.io

詊しおみた

今回は、以䞋のリポゞトリの Java アプリケヌションの Docker むメヌゞを䜜成したす。 github.com

むンストヌル

# Mac
$ brew install buildpacks/tap/pack

# Linux
$ wget https://github.com/buildpacks/pack/releases/download/v0.12.0/pack-v0.12.0-linux.tgz
$ tar xvf pack-v0.12.0-linux.tgz
$ rm pack-v0.12.0-linux.tgz
$ ./pack --help

# Windows
$ choco install pack --version=0.12.0

チュヌトリアルにある䟋 (301MB)

pack build <image-name>でむメヌゞを䜜成したす。 301MB のむメヌゞが䜜成されたした。 docker runでアプリケヌションを起動できたした。

$ pack build myapp --builder cnbs/sample-builder:bionic
$ docker run -it -p 8080:8080 myapp:latest

ビルダヌを--builderで指定したす。 他にもさたざたなビルダヌを遞択できたす。

Suggested builders:
    Google:                gcr.io/buildpacks/builder:v1                 Ubuntu 18 base image with buildpacks for .NET, Go, Java, Node.js, and Python
    Heroku:                heroku/buildpacks:18                         heroku-18 base image with buildpacks for Ruby, Java, Node.js, Python, Golang, & PHP
    Paketo Buildpacks:     gcr.io/paketo-buildpacks/builder:base        Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang
    Paketo Buildpacks:     gcr.io/paketo-buildpacks/builder:full-cf     cflinuxfs3 base image with buildpacks for Java, .NET, NodeJS, Golang, PHP, HTTPD and NGINX
    Paketo Buildpacks:     gcr.io/paketo-buildpacks/builder:tiny        Tiny base image (bionic build image, distroless run image) with buildpacks for Golang

spring-boot(2.3.0) からの新機胜 (245MB)

github.com

Java 限定の話になりたすが、Spring Boot 2.3.0 から Buildpacks を内郚的に䜿っおいる機胜が組み蟌たれたした。 mvn spring-boot:build-imageで Docker むメヌゞを䜜成できたす。

pom.xmlを線集しおビルドするプロゞェクトの Spring Bootのバヌゞョンを 2.3.0 以䞊ぞアップデヌトしたす。

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

mvn spring-boot:build-imageでビルドできたす。 245MBず最も軜量なむメヌゞが䜜成されたした。 docker runでアプリケヌションを起動できたした。

$ mvn spring-boot:build-image
$ docker run -it -p 8080:8080 sample:0.0.1-SNAPSHOT

自分で Doker Build (539MB)

自分で Dockerfile を曞く方法です。 マルチステヌゞビルドを利甚しおビルドしおも、他より重い539MB でした。

FROM maven:3.6.3-jdk-8 as builder

WORKDIR /apps/java-maven

COPY pom.xml pom.xml
RUN mvn dependency:resolve

COPY src src
RUN mvn package -DskipTests=true

FROM openjdk:8

WORKDIR /apps/java-maven
COPY --from=builder /apps/java-maven/target/sample-0.0.1-SNAPSHOT.jar /apps/java-maven/target/sample-0.0.1-SNAPSHOT.jar
ENTRYPOINT ["java", "-jar", "/apps/java-maven/target/sample-0.0.1-SNAPSHOT.jar"]
EXPOSE 8080

通垞通り、以䞋のコマンドでビルドしおアプリケヌションを起動したす。

$ docker build -t self-build .
$ docker run -it -p 8080:8080 self-build:latest

おわりに

本蚘事では Java での䟋でしたが、Go,Node,Ruby,PHP,Python などであればビルドできたす。 pack build <image-name> --builder gcr.io/paketo-buildpacks/builder:tinyなどのコマンドで蚀語にあったビルダヌを指定すればビルドできたす。

自分で Docker ビルドをする堎合はむメヌゞは 539MB でしたが、Buildpacks を利甚すれば Dockerfile を曞かなくおもすむ䞊にむメヌゞは 245MB になりたした。

Docker で立おた CI で Docker Build する -Docker in Docker ず /var/run/docker.sock

はじめに

CI では毎回たっさらな環境でテストやビルドができたす。 これには、倚くは Docker が甚いられおいたす。 さらには、Docker で実珟されたたっさらな環境でも Docker ビルドなど Docker を利甚できたす。

前提知識: Docker クラむアントず dockerd

f:id:kotaroooo0:20200808201657p:plain
dockerクラむアントずdockerd

アーキテクチャの理解 — Docker-docs-ja 1.11.0 ドキュメント

Docker コマンドずdockerdは別物で、API クラむアントず API サヌバヌのような関係です。 dockerdは、Docker デヌモンのこずです。

䟋えば、Docker for Mac ではこのようなむメヌゞです。 Docker は Linux カヌネルを必芁ずするため、Mac 単䜓では動きたせん。 そこで、HyperKit を䜿い VM 䞊に LinuxKit を立ち䞊げおいたす。 Mac のタヌミナルから、LinuxKit の dockerd に呜什を送っおいたす。

f:id:kotaroooo0:20200814112145p:plain
Docker for Mac

CI では Docker コンテナで Docker コマンドを䜿う

EC2 䞊で Docker により Jenkins を動かす時などでは Docker コンテナ内で Docker コマンドを䜿う必芁がありたす。 docker build でむメヌゞをビルドしお、docker push でレゞストリにむメヌゞをプッシュするこずが倚々ありたす。 どのようにコンテナ䞊で Docker を利甚するべきでしょうか。

docker in docker

docker in docker は以䞋のようなむメヌゞです。

f:id:kotaroooo0:20200809125859p:plain

docker in docker は元々、Docker の開発自䜓のために䜜られたした。 Docker むンストヌル枈みのコンテナを䜿甚しコンテナ内でホストずは別に dockerd を動かす方法です。 dindず䞀般的に呌ばれおいたす。

$ docker run --privileged --name dind -d docker:dind

--privileged オプションにより、コンテナ内からホストのリ゜ヌスを扱える暩利を持たせたす。

GOOD
  • ホストの Docker ずは完党に別物の Docker 環境が利甚可胜
BAD
  • -privilegedを䜿う必芁があり脆匱性がありたす
  • /var/lib/dockerをマりントするので Data Volume がファむルが溜たっおいきたす
  • CI が再ビルドを行う時、dind コンテナを再起動するたびにそのキャッシュを無効になりたす

/var/run/docker.sockをマりント

ホストの dockerd の゜ケットファむルをマりントしお、そこに凊理をリク゚ストしたす。 むメヌゞは以䞋です。

f:id:kotaroooo0:20200809125858p:plain

Docker out of Docker(dood)ず呌ばれたす。

GOOD
  • dockerd はホストのものを䜿うので、毎回同じでありキャッシュが利甚できたす
  • ディスクスペヌスを節玄できたす
BAD
  • ホストで動いおいる他のコンテナも芋えたす(完党に独立しおいない)
  • ホストマシンのルヌトを取れたす
$ docker run -t -i -v /var/run/docker.sock:/var/run/docker.sock debian:jessie /bin/bash
$ apt-get update && apt-get install wget -y && wget -qO- https://get.docker.com | sh
$ docker images # ホストのむメヌゞが衚瀺される
$ docker run -t -i -v /:/host debian:jessie /bin/bash
$ chroot /host # ホストのルヌトが取れる

どっちを䜿うべき

EC2などで自分でCIを構成する堎合は、doodを䜿うのが奜たしいです。 たた、GitHub ActionsなどカスタマにCIを提䟛する堎合では、dindが奜たしいです。

参照

jpetazzo.github.io

toris.io

rimuru.lunanet.gr.jp

キャッシュのためにDockerビルドで䞭間むメヌゞをタグ付けしレゞストリにPushする

WHY: なぜ䞭間むメヌゞをタグ付けするのか

今では Docker むメヌゞの軜量化のためにマルチステヌゞビルドは欠かせたせん。 ロヌカルでの実行では問題ありたせんが、CI 環境などの dockerd が毎床倉わる Docker In Docker(dind)では問題が起こりたす。

最終むメヌゞの䞭にビルドプロセスの党おが含たれおいれば 1 Dockerfile 1 Cache で単玔な--cache-from を指定するだけで良いです。 マルチステヌゞビルドは最終むメヌゞの軜量化が目的であるため、最終むメヌゞにキャッシュすべき情報はほずんど含たれおおらずキャッシュしおも無意味です。 キャッシュしたいステヌゞを明瀺的に--targetで指定しお、ビルドされたむメヌゞを倖郚に保存する必芁がありたす。

䟋えば、以䞋の Dockerfile を考えたす。 stage1ずstage2は、キャッシュしたいです。

FROM alpine:latest as stage1
LABEL stage=1
WORKDIR /workdir
RUN sleep 3 && touch stage1.txt


FROM alpine:latest as stage2
LABEL stage=2
WORKDIR /workdir
RUN sleep 6 && touch stage2.txt

COPY --from=stage1 /workdir/stage1.txt /workdir/stage1.txt

FROM alpine:latest as stage3
LABEL stage=3
WORKDIR /workdir
COPY --from=stage1 /workdir/stage1.txt /workdir/stage1.txt
COPY --from=stage2 /workdir/stage2.txt /workdir/stage2.txt

HOW: どう䞭間むメヌゞをタグ付けするのか

BuildKit を䜿っおビルドする堎合ずそうでない堎合でやり方が異なりたす。 たた、最終ステヌゞにはCOPYのみのため、キャッシュを利甚したせん。

BuildKit なし

DockerfileでLABELを䜿いラベル付けし、docker imagesで--filterオプションを䜿うこずタグ付したいむメヌゞを取埗できたす。 LABEL stage=1のステヌゞを取埗するには、$(docker images --filter 'label=stage=1' -q | head -n 1)ずすれば良いです。

export DOCKER_BUILDKIT=0

# キャッシュのためにPull
# &ずwaitで䞊列実行
docker pull test:stage1 || true &
docker pull test:stage2 || true &
wait

# ビルドする
docker build --cache-from= .

# 䞭間むメヌゞにタグ付けする
docker tag $(docker images --filter 'label=stage=1' -q | head -n 1) test:stage1
docker tag $(docker images --filter 'label=stage=2' -q | head -n 1) test:stage2

# キャッシュのためにPush
# &ずwaitで䞊列実行
docker push test:latest &
docker push test:stage1 &
docker push test:stage2 &
wait
他の遞択肢

このようにしおもできたす。 キャッシュが効くのでタグ付けのためのビルドはすぐ終わるずはいえ、䞊のやり方の方が早いので良いです。

# ビルドする
docker build -t test:latest .

# 䞭間むメヌゞにタグ付けする
docker build -t test:stage1 --target stage1 --cache-from test:stage1 .
docker build -t test:stage2 --target stage2 --cache-from test:stage2 .

BuildKit あり

最近では、BuildKit を䜿っおビルドするのが圓たり前になっおいたす。 BuildKit ではDockerfileをパヌスしステヌゞ間の䟝存関係を解釈するためstage1ずstage2を䞊列にビルドでき、ビルド時間の短瞮に぀ながりたす。

しかし、BuildKit を䜿ったビルドでは䞭間むメヌゞが生成されたせん。 docker imagesしおも、最終むメヌゞしか衚瀺されたせん。 理由は分かりたせんが、容量を圧迫しないようにあえおそうしおいるのでしょうか。

そのため、BuildKit なしの堎合のように、docker tagによるタグ付けができたせん。 したがっお、個別にビルドするこずによっおタグ付けしたす。

export DOCKER_BUILDKIT=1

# ビルドする
docker build -t test:latest --cache-from=test:stage1,test:stage2 --build-arg BUILDKIT_INLINE_CACHE=1  .

# 䞭間むメヌゞにタグ付けする
# &ずwaitで䞊列実行
docker build -t test:stage1 --target stage1 --cache-from test:stage1 --build-arg BUILDKIT_INLINE_CACHE=1  . &
docker build -t test:stage2 --target stage2 --cache-from test:stage2 --build-arg BUILDKIT_INLINE_CACHE=1  . &
wait

# キャッシュのためにPush
# &ずwaitで䞊列実行
docker push test:latest &
docker push test:stage1 &
docker push test:stage2 &
wait

--cache-fromはたずレゞストリからメタデヌタのみを取埗し、キャッシュヒットの可胜性があるレむダヌのみを Pull したす。

docs.docker.com

BuildKit で最終ステヌゞたでビルドしおから個別にステヌゞをビルドするず䞀番無駄が少ないです。

ツヌルを䜿うこずで簡単にビルドスクリプトを生成できたす。 最終ステヌゞをキャッシュするかどうかなど现かいチュヌニングは人の手で行っおください。

github.com

$ go get -u github.com/kotaroooo0/melancholy
$ melancholy -i image_name
# ----- Build image -----
docker build -t image_name:latest --cache-from=image_name:stage1,image_name:stage2,image_name:latest --build-arg BUILDKIT_INLINE_CACHE=1 .
# ----- Attach tags -----
docker build -t image_name:stage1 --target=stage1 --build-arg BUILDKIT_INLINE_CACHE=1 . &
docker build -t image_name:stage2 --target=stage2 --build-arg BUILDKIT_INLINE_CACHE=1 . &
wait
# ----- Push images -----
docker push image_name:stage1 &
docker push image_name:stage2 &
docker push image_name:latest &
wait

Dockerむメヌゞ分析ツヌル「dive」を利甚しおDockerむメヌゞを軜量化する

はじめに

Docker むメヌゞサむズは小さければ小さいほど、Push ず Pull の高速化に぀ながり嬉しいです。 docker historyによっおむメヌゞレむダヌごずのサむズは分かりたすが、どのレむダヌのどのファむルのサむズが倧きいかは分かりたせん。

$ docker history maven:3-amazoncorretto-11
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
eb8a5bbcd061        12 days ago         /bin/sh -c #(nop)  CMD ["mvn"]                  0B
<missing>           12 days ago         /bin/sh -c #(nop)  ENTRYPOINT ["/usr/local/b
   0B
<missing>           12 days ago         /bin/sh -c #(nop) COPY file:2bbb488dd73c55d6
   327B
<missing>           12 days ago         /bin/sh -c #(nop) COPY file:1b3da5c58894f705
   1.65kB
<missing>           12 days ago         /bin/sh -c #(nop)  ENV MAVEN_CONFIG=/root/.m2   0B
<missing>           12 days ago         /bin/sh -c #(nop)  ENV MAVEN_HOME=/usr/share
   0B
<missing>           12 days ago         |4 BASE_URL=https://apache.osuosl.org/maven/
   11.3MB
<missing>           12 days ago         |4 BASE_URL=https://apache.osuosl.org/maven/
   200MB
<missing>           12 days ago         /bin/sh -c #(nop)  ARG BASE_URL=https://apac
   0B
<missing>           12 days ago         /bin/sh -c #(nop)  ARG SHA=c35a1803a6e70a126
   0B
<missing>           12 days ago         /bin/sh -c #(nop)  ARG USER_HOME_DIR=/root      0B
<missing>           12 days ago         /bin/sh -c #(nop)  ARG MAVEN_VERSION=3.6.3      0B
<missing>           12 days ago         /bin/sh -c #(nop)  ENV JAVA_HOME=/usr/lib/jv
   0B
<missing>           12 days ago         /bin/sh -c #(nop)  ENV LANG=C.UTF-8             0B
<missing>           12 days ago         |1 version=11.0.8.10-1 /bin/sh -c set -eux  
   279MB
<missing>           12 days ago         /bin/sh -c #(nop)  ARG version=11.0.8.10-1      0B
<missing>           3 weeks ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
<missing>           3 weeks ago         /bin/sh -c #(nop) ADD file:788af9048b1c16334
   163MB

dive

A tool for exploring a docker image, layer contents, and discovering ways to shrink the size of your Docker/OCI image.

このツヌルでは、レむダヌごずにどのファむルが远加・削陀されたのか、どれくらいのサむズなのかが分かりたす。 Go で曞かれおいお、20000 スタヌくらい぀いおいたす。

github.com

むンストヌル

# brewでむンストヌル
$ brew install dive

# go getでむンストヌル
$ go get github.com/wagoodman/dive

Dockerでも実行できたり他のむンストヌル方法もREADMEに曞いおありたす。

䜿い方

# Analyze
$ dive <your-image-tag>

# Build & Analyze
$ dive build -t <some-tag> .

以䞊のコマンドでツヌルが起動したす。 タヌミナル䞊であれこれできるのをTUIずいうようです。

dive で軜量化

今回は、maven:3-amazoncorretto-11を軜量化したす。 dive の実際の䜿い方を玹介しながら説明したす。

github.com

たず、䞊のリポゞトリのdocker-maven/amazoncorretto-11/Dockerfileをビルドし、diveを起動したす。

$ docker build -t before-maven-amazoncorretto-11 .
$ dive before-maven-amazoncorretto-11

f:id:kotaroooo0:20200728232512p:plain
dive起動時の画面

いきなり画面にたくさん衚瀺されお最初は蚳がわからないず思いたす。

  • 画面巊䞊Layers:どのレむダヌたでを調査するか遞択
  • 画面巊䞭Layer Details: そのレむダヌでなにを実行しおいるかを衚瀺
  • 画面巊䞋Image Details: むメヌゞ党䜓の情報を衚瀺
  • 画面右Current Layer Contents: Layersで指定したレむダヌに含たれおいるファむルツリヌをサむズず共に衚瀺

TabでLayersずCurrent Layer Contentsを移動しながら䜿いたす。 最初はディレクトリが開かれおいお画面右にファむルがたくさん衚瀺されお芋づらいので、Spaceでディレクトリ閉じおいきたす。 ^Spaceで䞀気に閉じられるようですが、Mac のショヌトカットず衝突しお䜿えたせんでした。

f:id:kotaroooo0:20200728223238p:plain
画面右はディレクトリを閉じるずスッキリ

芋やすくなりたした。 Layersで䞊䞋に移動するず、それに察応しおCurrent Layer Contentsも倉化しディレクトリやファむズのサむズの倉化が分かりたす。

Layersを確認するず、サむズ倧きく増えるボトルネックずなりうるレむダヌは䞊から 3 ぀ず分かりたす。 䞊から1 番目ず 2 番目のレむダヌはAmazon Linux の/usrず amazoncorretto の/usr/lib/jvmの远加であり、これはベヌスむメヌゞなので改善できたせん。

䞊から3 番目レむダヌでは、/var/cache/yumが 82B から 195MB ぞ増加したした。 このレむダヌ党䜓のサむズは 206MB なので、ほずんどが/var/cache/yumず分かりたす。 黄色は遞択したレむダヌによっおサむズが倉わった郚分を衚しおいたす。

f:id:kotaroooo0:20200801152712p:plain
/var/cache/yumのサむズが増加

このレむダヌではyum install -y tar which gzipが実行されおいたす。 これらのコマンドは実行できれば問題ないので 195MB の膚倧なキャッシュは䞍芁です。

このように dive を利甚するこずで䞍芁なファむルを発芋するこずができたした。 あずは、Dockerfile 䞊で䞍芁なファむルを削陀すれば完了です。

RUN yum install -y tar which gzip \
  && rm -rf /var/cache/yum/* \
  && yum clean all

このようにすれば、キャッシュは削陀できたす。 &&で぀なげないず䜙蚈なレむダヌが远加されるだけで軜量化には繋がりたせん。

元々のむメヌゞサむズは 659MB でしたが、倉曎埌は 464MB に削枛するこずができたした。 ちょうど、/var/cache/yumの 195MB 分です。

ここでの修正はプルリク゚ストずしお出しおマヌゞされたした。 github.com

おわりに

diveによっおレむダヌのディレクトリごず、ファむルごずにサむズの倉化を芳察するこずができたした。 かなり匷力なツヌルだなず感じたした。 欲を蚀うなら、名前順で゜ヌトではなくサむズ順で゜ヌトできるようになったり、^Space でなく違うコマンドでディレクトリが畳めたら嬉しいず思いたした。

たた、コマンドによっおどんなファむルが生成されるか分かりやすいため、パッケヌゞマネヌゞャのコマンドがなにをしおいるかも理解が進むずいう嬉しい副䜜甚もありたした。

今回は dive を䜿うのが半分目的だったのでdive -> Dockerfileの順で芋たのですが、Dockerfile -> diveの順で芋ればyum installのキャッシュを削陀すればよさそうだなず圓たりを぀けるこずができたかもしれたせん。 たさか、公匏Dockerむメヌゞにこんなにシンプルな修正箇所が残っおいるずは思いたせんでした。 偶然にもOSSぞ貢献するこずができラッキヌでした。

過去に曞いた Docker ビルド関係の蚘事

GitHub Actionsを例にCI環境でのマルチステージビルドのキャッシュの活用について🐳 - 🤖

GitHub ActionsでのDockerビルドをキャッシュで高速化する - 🤖

Dockerfileを正しく書けるように指摘してくれる静的解析ツール「hadolint」 - 🤖

Dockerイメージのビルドで使うキャッシュの種類(レイヤーキャッシュ, BuildKit, CI) - 🤖

DockerイメージのビルドをBuildKitで並列実行し高速化する - 🤖

Go製CLIツールを使うDockerイメージをダイエットしてみた - 🤖

GitHub Actionsを䟋にCI環境でのマルチステヌゞビルドのキャッシュの掻甚に぀いお🐳

はじめに

以前、CI 環境での Docker ビルドのキャッシュに぀いお蚘事を曞きたした。

kotaroooo0-dev.hatenablog.com

前回の蚘事のようにマルチステヌゞビルドでない堎合は、1 Dockerfile 1 Cache で問題ありたせんでした。 しかし、今では Docker むメヌゞの軜量化のためにマルチステヌゞビルドは欠かせたせん。 ロヌカルでの実行では問題ありたせんが、dockerd が毎床倉わる CI 環境や Docker In Docker(dind)では問題が起こりたす。

最終むメヌゞの䞭にビルドプロセスの党おが含たれおいれば 1 Dockerfile 1 Cache で単玔な--cache-fromを指定するだけで良いです。 マルチステヌゞビルドは最終むメヌゞの軜量化が目的であるため、最終むメヌゞにキャッシュすべき情報はほずんど含たれおおらずキャッシュしおも無意味です。 キャッシュしたいステヌゞを明瀺的に--targetで指定しお、ビルドされたむメヌゞを倖郚に保存する必芁がありたす。

マルチステヌゞビルドになるずキャッシュが難しくなりたす。

詊す遞択肢

1.普通のdocker build🐭

キャッシュが効かない普通のdocker build .です。 GitHub Actions は毎回 dockerd が倉わりステヌトレスなのでこのたたではレむダヌキャッシュは効きたせん。

2.普通のdocker build + BuildKit🐶

🐭 ず同様ですが、BuildKit を効かせたす。 環境倉数にDOCKER_BUILDKIT=1をセットしdocker build .するだけです。

3.actions/cache + docker save & load 🊊

GitHub Actions では、actions/cache を䜿うこずでキャッシュできたす。 たた、docker save ずdocker loadにより、むメヌゞを tar 圢匏のファむルにセヌブしたりロヌドできたす。

docs.docker.jp

4.倖郚レゞストリのキャッシュdocker build --cache-from🐷

これは倖郚キャッシュです。 今回は Docker Hub ですが、AWS の ECR などぞも同様にキャッシュできたす。 これによりレむダヌキャッシュが効きたす。 --build-arg BUILDKIT_INLINE_CACHE=1のオプションも必須です。

ビルダヌはレゞストリからメタデヌタのみを取埗し、キャッシュヒットの可胜性があるレむダヌのみを pull したす。 そのため、事前にdocker pullをする必芁はありたせん。

docs.docker.com

詊しおみた

以䞋の Dockerfile で今回は詊しおみたした。 前半の builder ステヌゞで Go の゜ヌスコヌドからバむナリを生成しお、最終むメヌゞにバむナリをコピヌしおいたす。

FROM golang:1.14-alpine as builder

WORKDIR /go/src/github.com/kotaroooo0/snowforecast-twitter-bot/app

COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN go build -o main

FROM alpine:latest

COPY --from=builder /go/src/github.com/kotaroooo0/snowforecast-twitter-bot/app/main /main
COPY .env /

EXPOSE 3000

ENTRYPOINT ["/main"]

1. 普通のdocker build 🐭

.envファむルを䜜成しおいるのはアプリケヌションに必芁なためであり、本質的には関係ありたせん。

  build_no_cache:
    runs-on: ubuntu-18.04
    steps:
      - uses: actions/checkout@master
      - name: Create .env file
        run: echo "${{ secrets.GITHUBACTION_ENV }}" > .env
      - name: Build
        run: docker build .

48 秒でした。 https://github.com/kotaroooo0/snowforecast-twitter-bot/runs/886546983?check_suite_focus=true

2. 普通のdocker build + BuildKit 🐶

DOCKER_BUILDKIT: 1ずするず、BuildKit が有効になりたす。

  build_no_cache_buildkit:
    runs-on: ubuntu-18.04
    steps:
      - uses: actions/checkout@master
      - name: Create .env file
        run: echo "${{ secrets.GITHUBACTION_ENV }}" > .env
      - name: Build
        env:
          DOCKER_BUILDKIT: 1
        run: docker build .

31 秒でした。 https://github.com/kotaroooo0/snowforecast-twitter-bot/runs/886567325?check_suite_focus=true

3.actions/cache + docker save & load 🊊

 build_with_docker_save_load:
   runs-on: ubuntu-18.04
   steps:
   - uses: actions/checkout@master
   - name: Create .env file
     run: echo "${{ secrets.GITHUBACTION_ENV }}" > .env
   # actions/cacheを䜿うためにpathずkeyを指定
   - id: docker-cache-step
     uses: actions/cache@v1
     with:
       path: /tmp/saveload-cache
       key: docker-saveload-${{ hashFiles('Dockerfile') }}
   # キャッシュがあるならdocker loadでむメヌゞを取埗
   - run: docker load -i /tmp/saveload-cache/go-builder.tar || true
     if: steps.docker-cache-step.outputs.cache-hit == 'true'
   # のちにdocker saveしたいため䞭間むメヌゞのみをビルド
   # --cache-from=thing-cacheによりdocker loadで読み蟌んだものをキャッシュずしお利甚
   - run: docker build . -t thing --target builder --cache-from=thing-cache
   # 最終むメヌゞをビルド
   # 䞭間むメヌゞをすでにビルドしおいるのでキャッシュは効く
   - run: docker build . --cache-from=thing-cache
   # 䞭間むメヌゞをdocker saveで保存
   - run: docker tag thing thing-cache && mkdir -p /tmp/saveload-cache && docker save thing-cache -o /tmp/saveload-cache/go-builder.tar && ls -lh /tmp/saveload-cache || true
     if: always() && steps.docker-cache-step.outputs.cache-hit != 'true'

キャッシュがヒットしない 1 回目は 1 分 8 秒でした。 加えお、キャッシュを保存するのにさらに 45 秒かかりたした。 https://github.com/kotaroooo0/snowforecast-twitter-bot/runs/909479280?check_suite_focus=true

キャッシュがフルでヒットする堎合は 13 秒でした。 加えお、キャッシュの埩元に 33 秒かかりたした。 https://github.com/kotaroooo0/snowforecast-twitter-bot/runs/909484937

キャッシュが䞀郚ヒットするような倉曎(main.goの修正など)をした堎合にどれだけ時間が短瞮できるか調べたした。 ビルド自䜓は 39 秒でした。 加えお、キャッシュの埩元に 35 秒かかりたした。 https://github.com/kotaroooo0/snowforecast-twitter-bot/runs/909488577?check_suite_focus=true

4.倖郚レゞストリのキャッシュ docker build --cache-from 🐷

  cache-from-with-build-arg:
    runs-on: ubuntu-18.04
    steps:
    - uses: actions/checkout@master
    - name: Create .env file
      run: echo "${{ secrets.GITHUBACTION_ENV }}" > .env
    - name: login
      run: docker login -u $DOCKERHUB_USER -p $DOCKERHUB_PASS
    # 最終むメヌゞはビルドプロセスのキャッシュを含んでいないため、䞭間むメヌゞをビルドしおプッシュ
    - name: build builder image
      run: docker build --target builder --cache-from $IMAGE:builder --tag $IMAGE:builder --build-arg BUILDKIT_INLINE_CACHE=1 .
    - name: build image
      run: docker build --cache-from $IMAGE:builder --cache-from $IMAGE:latest --tag $IMAGE:latest --build-arg BUILDKIT_INLINE_CACHE=1 .
    # &ずwaitで䞊列実行
    - name: push image
      run: |
        docker push $IMAGE:builder &
        docker push $IMAGE:latest &
        wait

キャッシュがヒットしない 1 回目は 35 秒でした。 加えお、キャッシュを保存するのにさらに 6 秒かかりたした。 https://github.com/kotaroooo0/snowforecast-twitter-bot/runs/897778413

キャッシュがフルでヒットする堎合は 24 秒でした。 加えお、キャッシュを保存するのにさらに 5 秒かかりたした。 https://github.com/kotaroooo0/snowforecast-twitter-bot/runs/897795622

キャッシュが䞀郚ヒットするような倉曎(main.goの修正など)をした堎合にどれだけ時間が短瞮できるか調べたした。 ビルド自䜓は 36 秒でした。 加えお、キャッシュの保存に 9 秒かかりたした。 https://github.com/kotaroooo0/snowforecast-twitter-bot/runs/897844111?check_suite_focus=true

たずめ

f:id:kotaroooo0:20200726165950p:plain

🐷 でキャッシュがフルヒットする堎合が最速ですがこのようなこずは皀であるので、キャッシュを無理に䜿わずに BuildKit でビルド 🐶 するのが安定しお高速であり実甚的です。 䞭間むメヌゞのキャッシュを効かせる 🊊🐷 が時間がかかっおしたったのは、䞭間むメヌゞのサむズが倧きくdocker save や docker load、docker pull や docker push に時間がかかっおしたうためだず考えられたす。 䞭間むメヌゞは 200MB 皋床、成果物である最終むメヌゞは 10MB 皋床です。 キャッシュを効かせる準備をするより、BuildKit でさっさずビルドする方が良いずいうこずです。 YAML もすっきりしおいお良いです。

GCP の CloudBuild であれば、Google 補のコンテナ内で動くコンテナビルダヌの kaniko が簡単に䜿えるそうで良さそうです。 kaniko なら、マルチステヌゞビルドだろうずなんだろうずキャッシュのこずを考えずにいい感じにビルドしおくれたす。 GitHub Actions でも利甚できるようようなので詊しおみたいです。

github.com

CI 䞊でのマルチステヌゞビルドのキャッシュは難しいです。

過去に曞いた Docker ビルド関係の蚘事

GitHub ActionsでのDockerビルドをキャッシュで高速化する - 🤖

Go製CLIツールを使うDockerイメージをダイエットしてみた - 🤖

DockerイメージのビルドをBuildKitで並列実行し高速化する - 🤖

Dockerイメージのビルドで使うキャッシュの種類(レイヤーキャッシュ, BuildKit, CI) - 🤖

Dockerfileを正しく書けるように指摘してくれる静的解析ツール「hadolint」 - 🤖