APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

Databricks × Terraform × Docker Image: 実践的な構築と CI/CD パイプライン

目次

はじめに

GLB事業部Lakehouse部のティダです。

前回の記事では、Terraform を使用した Databricks リソースのデプロイの自動化について紹介しました。
前編の記事はこちらです。 https://techblog.ap-com.co.jp/draft/entry/PIuJdBvdBPoiP8oI7p6Ql5FXjxg

今回はその第2弾として、Terraform × Docker Image × Databricks をテーマに、より実践的な環境構築から検証手順、
さらに CI/CD パイプラインまでを解説します。

特に本記事では以下をカバーします:


背景と課題感

Databricks の標準クラスタは柔軟ですが、以下の課題があります:

  • 特定ライブラリや依存関係を毎回インストールするのが面倒

  • 構成管理が手作業になりやすい

  • チーム開発で環境差異が生じやすい

これを解決するアプローチが「Docker イメージによるクラスタベース環境の統一」です。
そして Terraform を用いてコード化することで、環境を再現可能・自動化可能にします。


全体アーキテクチャ

開発者 → GitHub (Dockerfile / Terraform / Notebook) → CI/CD → コンテナレジストリ → Databricks クラスタ (Docker Image)

ポイントは以下です:

  • Dockerfile: Python/R/Scala ライブラリを含むカスタムイメージを定義

  • Terraform: Databricks ワークスペース / クラスタをコード化

  • CI/CD: GitHub Actions / Azure DevOps 等で自動ビルド・デプロイ


前提条件

Databricks Container Service の有効化が必要
Databricks ワークスペースで Container Service 機能が有効になっている必要があります。
これはワークスペース作成時またはワークスペース設定で有効化できます。

手順

1. Docker イメージの作成

FROM databricksruntime/standard:latest
RUN pip install pandas scikit-learn mlflow

これをローカルでビルドし、任意のレジストリ(例: Docker Hub, ACR, GCR)にプッシュします。

docker build -t my-databricks-image:latest .
docker push myregistry/my-databricks-image:latest

2. Terraform でクラスタを定義

resource "databricks_cluster" "custom" {
  cluster_name            = "custom-docker-cluster"
  spark_version           = "13.3.x-scala2.12"
  node_type_id            = "Standard_DS3_v2"
  autotermination_minutes = 60
  num_workers             = 2

  docker_image {
    url = "myregistry/my-databricks-image:latest"
  }
}

3. 検証の進め方

検証では以下を確認します:

  1. クラスタが正常に作成されるか

  2. Docker イメージのライブラリが利用可能か(例: Notebook で import pandas が動作する)

  3. Terraform コードの terraform apply / destroy で環境が再現可能か


CI/CD への統合

本構成をチーム開発に組み込むには、CI/CD が不可欠です。以下の流れを想定します:

  1. GitHub Actions で Docker Build & Push

name: Build and Push Docker Image
on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Login to Registry
        run: echo ${{ secrets.REGISTRY_PASSWORD }} | docker login myregistry.io -u ${{ secrets.REGISTRY_USERNAME }} --password-stdin

      - name: Build Docker image
        run: docker build -t myregistry.io/my-databricks-image:${{ github.sha }} .

      - name: Push Docker image
        run: docker push myregistry.io/my-databricks-image:${{ github.sha }}

  1. Terraform Apply を自動化

name: Deploy Databricks Infra
on:
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
      - name: Terraform Init
        run: terraform init
      - name: Terraform Apply
        run: terraform apply -auto-approve

これにより、

  • main ブランチにマージすると Docker イメージが自動ビルド & プッシュ

  • 必要に応じて Terraform がクラスタを更新

という CI/CD サイクルを構築できます。


まとめ

本記事では、Databricks × Terraform × Docker Image を用いた実践的な構築方法を解説しました。ポイントは以下です:

  • Docker イメージで依存関係を統一し、クラスタ環境を安定化

  • Terraform で環境をコード化し、再現性を確保

  • CI/CD で自動ビルド & デプロイを実現

おわりに

前編・後編で Terraform を用いた Databricks 環境構築の基本的な設定を紹介しましたが、今回取り扱わなかった設定もまだ多く存在します。
たとえば、複雑なジョブ依存関係や詳細なライブラリ管理、ネットワークやセキュリティ周りの設定などです。
すべてのリソースを完全に管理するには、さらに理解を深める必要があると感じました。
興味のある方は、Terraform や Databricks の公式ドキュメントを参照すると、より高度な構成やベストプラクティスを学ぶことができます。
Terraform Registry
本記事を参考に、自分の環境に合わせた Databricks × Terraform × Docker の構築をぜひ試してみてください。

最後までご覧いただきありがとうございます。
私たちはDatabricksを用いたデータ分析基盤の導入から内製化支援まで幅広く支援をしております。
もしご興味がある方は、お問い合わせ頂ければ幸いです。

www.ap-com.co.jp

そして、一緒に働いていただける仲間も募集中です!
APCにご興味がある方の連絡をお待ちしております。

www.ap-com.co.jp

[Dagger] コードでDockerイメージをBuildする

はじめに

こんにちはACS事業部の谷合です。
みなさんDaggerなるツールをご存じでしょうか?
Daggerとは、数か月前にコードでCI/CDを実行するツールとしてCloudNative界隈を騒がせたツールです。 dagger.io このDagger、私が知ったのはTwitterでした。
その時は単にコードでCI/CDを実行するためだけのツールとの認識でしたが、最近思い立って調査してみるとコードでBuildができる機能が生えていましたので、今回は私の主言語であるGo言語でBuildをする方法をご紹介したいと思います。

Dockerfileを用いてBuild

今回は以下のDokcerfileを用いて、Buildしてみました。

FROM golang:1.19

WORKDIR /workspace
COPY go.mod go.mod
RUN go mod download

# Copy the go source
COPY main.go main.go

# Build
RUN CGO_ENABLED=0  go build -a -o myapp main.go

ENTRYPOINT ["/myapp"]

コードとしては以下のようになり、/home/taniai/dagger-testディレクトリにあるDockerfileを読み取り、BuildしてAzure Container Registry(以降ACR)にPushしているだけの単純なコードとなります。

package main

import (
    "context"
    "fmt"
    "os"

    "dagger.io/dagger"
)

func main() {
    ctx := context.Background()
    client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
    if err != nil {
        panic(err)
    }
    defer client.Close()

    addr, err := client.Host().Directory("/home/taniai/dagger-test").
        DockerBuild().
        Publish(ctx, "taniaitest.azurecr.io/myexample:with-dockerfile")
    if err != nil {
        panic(err)
    }
    fmt.Printf("Published to %s", addr)
}

これをBuildすることで以下のような出力が得られます。

$ go run main.go 
4: upload /home/taniai/dagger-test DONE
4: > in host.directory /home/taniai/dagger-test
4: upload /home/taniai/dagger-test DONE

4: upload /home/taniai/dagger-test
4: > in host.directory /home/taniai/dagger-test
4: transferring /home/taniai/dagger-test: 
4: transferring /home/taniai/dagger-test: 2.828KiB [0.01s]
4: upload /home/taniai/dagger-test DONE

3: copy /home/taniai/dagger-test CACHED
3: > in host.directory /home/taniai/dagger-test
3: copy /home/taniai/dagger-test CACHED

3: copy /home/taniai/dagger-test CACHED
3: > in host.directory /home/taniai/dagger-test
3: > in docker build
3: copy /home/taniai/dagger-test CACHED

4: upload /home/taniai/dagger-test DONE
4: > in host.directory /home/taniai/dagger-test
4: > in docker build
4: transferring /home/taniai/dagger-test: 2.828KiB [0.01s]
4: upload /home/taniai/dagger-test DONE

12: [builder 1/6] FROM docker.io/library/golang:1.19@sha256:405b708c4612817e9966bff1469570eaed1ecf02a98055fb43bd6ca7b6401b37
12: > in docker build
12: resolve docker.io/library/golang:1.19@sha256:405b708c4612817e9966bff1469570eaed1ecf02a98055fb43bd6ca7b6401b37 
12: ...

13: [stage-1 1/3] FROM gcr.io/distroless/static:nonroot@sha256:9ecc53c269509f63c69a266168e4a687c7eb8c0cfd753bd8bfcaa4f58a90876f DONE
13: > in docker build
13: resolve gcr.io/distroless/static:nonroot@sha256:9ecc53c269509f63c69a266168e4a687c7eb8c0cfd753bd8bfcaa4f58a90876f [0.07s]
13: [stage-1 1/3] FROM gcr.io/distroless/static:nonroot@sha256:9ecc53c269509f63c69a266168e4a687c7eb8c0cfd753bd8bfcaa4f58a90876f DONE

12: [builder 1/6] FROM docker.io/library/golang:1.19@sha256:405b708c4612817e9966bff1469570eaed1ecf02a98055fb43bd6ca7b6401b37 DONE
12: > in docker build
12: resolve docker.io/library/golang:1.19@sha256:405b708c4612817e9966bff1469570eaed1ecf02a98055fb43bd6ca7b6401b37 [0.08s]
12: [builder 1/6] FROM docker.io/library/golang:1.19@sha256:405b708c4612817e9966bff1469570eaed1ecf02a98055fb43bd6ca7b6401b37 DONE

9: [builder 3/6] COPY go.mod go.mod CACHED
9: > in docker build
9: [builder 3/6] COPY go.mod go.mod CACHED

10: copy / / CACHED
10: > in docker build
10: copy / / CACHED

11: [builder 2/6] WORKDIR /workspace CACHED
11: > in docker build
11: [builder 2/6] WORKDIR /workspace CACHED

14: [stage-1 2/3] COPY --from=builder /workspace/myapp . CACHED
14: > in docker build
14: [stage-1 2/3] COPY --from=builder /workspace/myapp . CACHED

15: [builder 6/6] RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o myapp main.go CACHED
15: > in docker build
15: [builder 6/6] RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o myapp main.go CACHED

16: [builder 5/6] COPY main.go main.go CACHED
16: > in docker build
16: [builder 5/6] COPY main.go main.go CACHED

17: [builder 4/6] RUN go mod download CACHED
17: > in docker build
17: [builder 4/6] RUN go mod download CACHED

18: exporting to image
18: exporting layers 
18: exporting layers [0.00s]
18: exporting manifest sha256:889a6d9431189999abbe39495a8e1526324ef5bbd899d2acfbfab24277a77d0e 
18: exporting manifest sha256:889a6d9431189999abbe39495a8e1526324ef5bbd899d2acfbfab24277a77d0e [0.02s]
18: exporting config sha256:369d542cb26dfdf5b711a00bc3dda04c65d580a6916d177e2bbaaee2f9f79f2c 
18: exporting config sha256:369d542cb26dfdf5b711a00bc3dda04c65d580a6916d177e2bbaaee2f9f79f2c [0.03s]
18: pushing layers 
18: pushing layers [0.35s]
18: pushing manifest for taniaitest.azurecr.io/myexample:with-dockerfile@sha256:889a6d9431189999abbe39495a8e1526324ef5bbd899d2acfbfab24277a77d0e 
18: pushing manifest for taniaitest.azurecr.io/myexample:with-dockerfile@sha256:889a6d9431189999abbe39495a8e1526324ef5bbd899d2acfbfab24277a77d0e [0.47s]
18: exporting to image DONE
Published to taniaitest.azurecr.io/myexample:with-dockerfile@sha256:889a6d9431189999abbe39495a8e1526324ef5bbd899d2acfbfab24277a77d0e

Dockerfileを使用せずにコードでBuild

DaggerはDockerfileを使用しない、コードでのBuildも可能です。
モデルとなるDockerfileは前セクションDockerfileを用いてBuildで記載したDockerfileで、こちらをコードで表現してみます。
WithDirectory関数で/home/taniai/dagger-testにあるファイルをすべて/workspaceディレクトリにコピーして、go buildしてACRにPushしています。

package main

import (
    "context"
    "fmt"
    "os"

    "dagger.io/dagger"
)

func main() {
    ctx := context.Background()
    client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
    if err != nil {
        panic(err)
    }
    defer client.Close()

    project := client.Host().Directory("/home/taniai/dagger-test")
    builder := client.Container().
        From("golang:1.19").
        WithDirectory("/workspace", project).
        WithWorkdir("/workspace").
        WithEnvVariable("CGO_ENABLED", "0").
        WithExec([]string{"go", "build", "-o", "myapp"})

    // publish the container image
    addr, err := builder.Publish(ctx, "taniaitest.azurecr.io/myapp:whithout-dockerfile")
    if err != nil {
        panic(err)
    }
    fmt.Printf("Published to %s", addr)
}

これをBuildすることで以下のような出力が得られます。
ちゃんとBuild & Pushできてますね。

$ go run main.go 
4: upload /home/taniai/dagger-test DONE
4: > in host.directory /home/taniai/dagger-test
4: upload /home/taniai/dagger-test DONE

4: upload /home/taniai/dagger-test
4: > in host.directory /home/taniai/dagger-test
4: transferring /home/taniai/dagger-test: 
4: transferring /home/taniai/dagger-test: 2.828KiB [0.01s]
4: upload /home/taniai/dagger-test DONE

3: copy /home/taniai/dagger-test CACHED
3: > in host.directory /home/taniai/dagger-test
3: copy /home/taniai/dagger-test CACHED

8: resolve image config for docker.io/library/golang:1.19
8: > in from golang:1.19
8: resolve image config for docker.io/library/golang:1.19 DONE

4: upload /home/taniai/dagger-test DONE
4: > in host.directory /home/taniai/dagger-test
4: transferring /home/taniai/dagger-test: 2.828KiB [0.01s]
4: upload /home/taniai/dagger-test DONE

3: copy /home/taniai/dagger-test CACHED
3: > in host.directory /home/taniai/dagger-test
3: copy /home/taniai/dagger-test CACHED

17: pull docker.io/library/golang:1.19
17: > in from golang:1.19
17: resolve docker.io/library/golang:1.19@sha256:405b708c4612817e9966bff1469570eaed1ecf02a98055fb43bd6ca7b6401b37 
17: resolve docker.io/library/golang:1.19@sha256:405b708c4612817e9966bff1469570eaed1ecf02a98055fb43bd6ca7b6401b37 [0.03s]
17: pull docker.io/library/golang:1.19 DONE

16: copy / /workspace CACHED
16: copy / /workspace CACHED

14: exec go build -o myapp
14: exec go build -o myapp DONE

18: exporting to image
18: exporting layers 
18: exporting layers [0.86s]
18: exporting manifest sha256:5b8b72ca28a376a48628094699bc4d76a5f8278380acc4167761fbd54302e453 
18: exporting manifest sha256:5b8b72ca28a376a48628094699bc4d76a5f8278380acc4167761fbd54302e453 [0.04s]
18: exporting config sha256:414bb776d51659fe945dd0186e0e61e77a19b78541a765ff5997eb1af99168fa 
18: exporting config sha256:414bb776d51659fe945dd0186e0e61e77a19b78541a765ff5997eb1af99168fa [0.03s]
18: pushing layers 
18: pushing layers [0.89s]
18: pushing manifest for taniaitest.azurecr.io/myapp:whithout-dockerfile@sha256:5b8b72ca28a376a48628094699bc4d76a5f8278380acc4167761fbd54302e453 
18: pushing manifest for taniaitest.azurecr.io/myapp:whithout-dockerfile@sha256:5b8b72ca28a376a48628094699bc4d76a5f8278380acc4167761fbd54302e453 [0.49s]
18: exporting to image DONE
Published to taniaitest.azurecr.io/myapp:whithout-dockerfile@sha256:5b8b72ca28a376a48628094699bc4d76a5f8278380acc4167761fbd54302e453

Dockerfileを用いないMulti-Stage Build

通常のBuildだけなく、Multi-Stage Buildも可能です。
以下のようなMulti-Stage Buildを行うDockerfileをコードで表現してみます。

# Build the myapp binary
FROM golang:1.19 as builder

WORKDIR /workspace
COPY go.mod go.mod
RUN go mod download

# Copy the go source
COPY main.go main.go

# Build
RUN CGO_ENABLED=0 go build -a -o myapp main.go

# Use distroless as minimal base image to package the myapp binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /workspace/myapp .
USER 65532:65532

ENTRYPOINT ["/myapp"]

以下のようなコードとなり、最初にbuilder変数に初回のBuildした結果を入れます。
その後、prodImage変数に次回のBuild結果を入れ、最後にACRにPushしています。

package main

import (
    "context"
    "fmt"
    "os"

    "dagger.io/dagger"
)

func main() {
    ctx := context.Background()
    client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr))
    if err != nil {
        panic(err)
    }
    defer client.Close()

    project := client.Host().Directory("/home/taniai/dagger-test")
    builder := client.Container().
        From("golang:1.19").
        WithDirectory("/workspace", project).
        WithWorkdir("/workspace").
        WithEnvVariable("CGO_ENABLED", "0").
        WithExec([]string{"go", "build", "-o", "myapp"})

    prodImage := client.Container().
        From("gcr.io/distroless/static:nonroot").
        WithFile("/workspace/myapp", builder.File("/workspace/myapp")).
        WithEntrypoint([]string{"/workspace/myapp"})

    // publish the container image
    addr, err := prodImage.Publish(ctx, "taniaitest.azurecr.io/myapp:multistage")
    if err != nil {
        panic(err)
    }
    fmt.Printf("Published to %s", addr)
}

結果は以下の通りとなり、Multi-Stage Buildができていることが確認できるはずです。

$ go run main.go 
4: upload /home/taniai/dagger-test DONE
4: > in host.directory /home/taniai/dagger-test
4: upload /home/taniai/dagger-test DONE

4: upload /home/taniai/dagger-test
4: > in host.directory /home/taniai/dagger-test
4: transferring /home/taniai/dagger-test: 
4: transferring /home/taniai/dagger-test: 2.828KiB [0.01s]
4: upload /home/taniai/dagger-test DONE

3: copy /home/taniai/dagger-test CACHED
3: > in host.directory /home/taniai/dagger-test
3: copy /home/taniai/dagger-test CACHED

8: resolve image config for docker.io/library/golang:1.19
8: > in from golang:1.19
8: resolve image config for docker.io/library/golang:1.19 DONE

17: pull docker.io/library/golang:1.19
17: > in from golang:1.19
17: resolve docker.io/library/golang:1.19@sha256:405b708c4612817e9966bff1469570eaed1ecf02a98055fb43bd6ca7b6401b37 
17: resolve docker.io/library/golang:1.19@sha256:405b708c4612817e9966bff1469570eaed1ecf02a98055fb43bd6ca7b6401b37 [0.04s]
17: pull docker.io/library/golang:1.19 DONE

16: copy / /workspace CACHED
16: copy / /workspace CACHED

14: exec go build -o myapp
14: exec go build -o myapp DONE

20: resolve image config for gcr.io/distroless/static:nonroot
20: > in from gcr.io/distroless/static:nonroot
20: resolve image config for gcr.io/distroless/static:nonroot DONE

4: upload /home/taniai/dagger-test DONE
4: > in host.directory /home/taniai/dagger-test
4: transferring /home/taniai/dagger-test: 2.828KiB [0.01s]
4: upload /home/taniai/dagger-test DONE

3: copy /home/taniai/dagger-test CACHED
3: > in host.directory /home/taniai/dagger-test
3: copy /home/taniai/dagger-test CACHED

14: exec go build -o myapp DONE
14: exec go build -o myapp DONE

17: pull docker.io/library/golang:1.19 DONE
17: > in from golang:1.19
17: resolve docker.io/library/golang:1.19@sha256:405b708c4612817e9966bff1469570eaed1ecf02a98055fb43bd6ca7b6401b37 [0.04s]
17: pull docker.io/library/golang:1.19 DONE

16: copy / /workspace CACHED
16: copy / /workspace CACHED

24: pull gcr.io/distroless/static:nonroot
24: > in from gcr.io/distroless/static:nonroot
24: resolve gcr.io/distroless/static:nonroot@sha256:9ecc53c269509f63c69a266168e4a687c7eb8c0cfd753bd8bfcaa4f58a90876f 
24: resolve gcr.io/distroless/static:nonroot@sha256:9ecc53c269509f63c69a266168e4a687c7eb8c0cfd753bd8bfcaa4f58a90876f [0.04s]
24: pull gcr.io/distroless/static:nonroot DONE

25: copy /workspace/myapp /workspace/myapp CACHED
25: copy /workspace/myapp /workspace/myapp CACHED

26: exporting to image
26: exporting layers 
26: exporting layers [0.00s]
26: exporting manifest sha256:9d7a9a45835b05c1bbb1527a22ea882c5bc453783762ba507f2a63bd3315219d 
26: exporting manifest sha256:9d7a9a45835b05c1bbb1527a22ea882c5bc453783762ba507f2a63bd3315219d [0.01s]
26: exporting config sha256:ab0284c5c2e4a2560df1e44e23af70d29903758f47dfc5b12d3ad39fa34c0e07 
26: exporting config sha256:ab0284c5c2e4a2560df1e44e23af70d29903758f47dfc5b12d3ad39fa34c0e07 [0.01s]
26: pushing layers 
26: pushing layers [0.13s]
26: pushing manifest for taniaitest.azurecr.io/myapp:multistagebuild@sha256:9d7a9a45835b05c1bbb1527a22ea882c5bc453783762ba507f2a63bd3315219d 
26: pushing manifest for taniaitest.azurecr.io/myapp:multistagebuild@sha256:9d7a9a45835b05c1bbb1527a22ea882c5bc453783762ba507f2a63bd3315219d [0.44s]
26: exporting to image DONE
Published to taniaitest.azurecr.io/myapp:multistagebuild@sha256:9d7a9a45835b05c1bbb1527a22ea882c5bc453783762ba507f2a63bd3315219d

さいごに

いかがでしたでしょうか?
DockerイメージをBuildするときはDockerfile必要でしょ!っと固定概念がありましたが、まさかコードで表現できるとは...
勉強になったと同時に、なかなか尖った検証ができ、楽しい時間を過ごせました。
普段は案件中心に業務を行っていますが、こういったOSSの検証はやはりエンジニアとして心躍るものがありますね。

今回ご紹介したコードでのBuildは、Daggerの本懐であるコードでのCI/CDでこそ活きてくるものだと思います。
GitHub Actionsなど優秀なツールが充実している分、なかなかCI/CDをコード化することはないと思いますが、CI/CD自体を内製化したいといった場合には活きてくる機能なのかなと感じました。

以下にDockerfileコマンドとそれに相当するDagger関数の対応についての記事とMulti-Stage Buildの方法のドキュメントも貼っておきますので、もしご興味あれば是非挑戦してみてください! dagger.io docs.dagger.io

ACS事業部のご紹介

私達ACS事業部はAzure・AKSなどのクラウドネイティブ技術を活用した内製化のご支援をしております。
www.ap-com.co.jp また、一緒に働いていただける仲間も募集中です!
今年もまだまだ組織規模拡大中なので、ご興味持っていただけましたらぜひお声がけください。 www.ap-com.co.jp

本記事の投稿者: 谷合純也
AKS/ACAをメインにインフラ系のご支援を担当しています。
junya0530さんの記事一覧 | Zenn

久しぶりにKubernetesの社内勉強会をやりました

f:id:smatsuzaki:20191213101717j:plain

システム基盤開発室の松崎です。 久しぶりとなるk8sの社内勉強会の話です。

APCでは、今年に入ってから有志メンバにて、kubernetesの社内勉強会を継続的に開催しており、年内最後となる5回目が開催されました。今までは、松崎が1H程度の発表を準備することが多かったのですが、今回は新たに2名の方にご発表頂き、総勢3名の発表と、いつもよりも豪華な会となりました!

発表内容は以下となります。

  • CloudNativeStorage, Rookの解説
  • GKE上でElasticsearchを動かす際の勘所
  • SpringBootでコンテナ対応のJavaAppに入門する

以下、簡単な雑感です。

Rookの話

  • CloudNative Storage, Rookの概要紹介とv1.1の新機能の話。KubeConの内容も踏まえ丁寧に整理された説明。流石です
  • 参加者の大半が「名前は知っているけど、詳細は知らない」という状態からスタート
  • 以外に対応するプロダクトが多い(が、Ceph目当てで使う人が圧倒的に多いとのこと)
  • 聞いていると次第に「Cephの知識がないとちゃんとこれは理解できないのでは?」という気持ちに..どこかで勉強します。。

Elasticsearch on GKEの話

  • GKE上でElasticsearchを動かす際に気を付けるべき点を、Elasticsearchのアーキテクチャを踏まえつつ説明
  • Shard/Replicaなどの基礎概念の復習になりました
  • Public cloud上でアプリを動かす際の zone awareness 的な仕組みは、耐障害性的な意味で広く需要がありそう!と感じました

SpringBootの話

  • 松崎にて発表させて頂きました!
  • SpringBootで簡単なWebAppを作ってコンテナ上で動かすためのチュートリアル的な話です
  • SpringBootにはk8s向きな機能が幾つかあるのでそのあたりの説明も
  • 本当はKubernetesでAppを動かすところまで話をしたかったのですが、準備時間の都合で、Docker止まりとなりました。無念。。

以上です。技術トレンドの変化のスピードが速いCloudNative業界にキャッチアップし続けるため、 来年もできるだけ勉強会を継続していけたらと考えています。

kubernetesソースコードリーディング 第4回

今回はちょっと趣向を変えて、テーマはDockerです!

kubernetesのソースコードからは少々離れてしまいますが、
コンテナランタイムもkubernetesの重要なコンポーネントの1つなので知っておいて損はないはず…!

アジェンダは以下になります。

コンテナ仮想化技術の歴史とdockerの特徴

コンテナ仮想化技術の歴史

著者は、Dockerで初めて "コンテナ" という言葉を知りましたが、
その概念自体はDocker登場のずっと前から存在する歴史あるものとのことです。

f:id:smatsuzaki:20190807142901p:plain
コンテナの歴史

“コンテナ仮想化技術”とは何か?

そもそもコンテナとは何でしょう?

広義のコンテナ仮想化技術とは、

隔離された環境におけるプログラムの実行、を実現する技術

と、定義されることが多いとのことです。

隔離(isolation)とは?

では、この場合の隔離という言葉は具体的に何を意味しているのでしょうか?

Dockerは、Linuxの名前空間機能を使用することで、
以下の6種類のリソースをホストOSから隔離します。

  • PID名前空間
  • Network名前空間
  • User名前空間
  • Mount名前空間
  • UTS名前空間
  • IPC名前空間

またDockerは、cgroups機能を使うことで、LinuxOS上のprocessとして実行されるプログラムに対し、 リソースへのアクセス制限を行います。

コンテナとしてのdockerの特徴

ホストOSのカーネルを共有

「コンテナ上のプロセスの実行に際し、ホストOSとコンテナ間でカーネルを共有する」というのがDockerのもう一つの特徴です。 OSがFedoraのホストマシンの上で、別のディストリビューションであるUbuntuのコンテナが動かせるはこの特徴があってこそといえます。LinuxKernelの Application Binary Interface (以下、ABI) は、常に後方互換性を維持するよう開発されているので、コンテナとホストでディストリビューション等の差異があっても、殆どのケースにおいてその差分は吸収されます。

ここで少し実験してみましょう。Fedoraが動作するホストOS上にて、
ubuntu:latest イメージのDockerコンテナを起動してみます。

マシン OS bash Linux Kernel
ホストマシン Fedora 4.4.23 5.1.6
Dockerコンテナ Ubuntu 4.4.19 -

f:id:chataro00:20190704174407p:plain

bash --version の実行結果から、ホストマシン/コンテナ内にて、bashについては、別々の実行ファイルが実行されていることが確認できます。 しかし、 uname -r の実行結果は同一のホストOSが持つカーネルを示しています。

上記から、以下のようなケースにおいて、コンテナ上のプログラムは実行に失敗すると推察されます。

f:id:chataro00:20190705153049p:plain

可搬性のあるimageとcontainer registry

コンテナ技術としてDockerの最後の特徴は、「Dockerイメージ」という器の中に、 任意のプログラム群をパッキングでき、それをDocker Hubをはじめとするcontainer registry上で push/pull させることができる点です。

では、その「Dockerイメージ」というのは具体的にはどのようなフォーマットなのでしょうか?

docker pullでイメージレイヤーを取ってきて、docker buildでさらにレイヤーを重ねて〜、というのは何となく理解していましたが、
その実体はと言われるとよくわかってなかったり…

f:id:chataro00:20190705154832p:plain

まずDocker Registry上で保持されるDockerイメージですが、distribution manifest というフォーマットで管理されているとのことでした。 distribution manifestはCPUアーキテクチャやVariants別に用意されており、
docker manifest inspect(要 experiment機能有効化)でそのデータ構造をJSON形式で見ることができます。
amd64のLinuxとラズパイ等で同じDockerイメージを使っているつもりが実体としては別物なんですね。

f:id:chataro00:20190705160537p:plain

f:id:chataro00:20190705171242p:plain

ユーザーはdocker CLIで取得したDocker Imageをdistribution manifestとは別の image manifest というフォーマットでlocalに展開します。 image manifestはdocker image saveコマンドでtarファイルとして出力できます。tarballを展開すると以下のようなファイルが確認できます。

  • layer(layer.tar)
    • 実際のdocker imageを構成する断片化したファイル群
  • config(Image config JSON)
    • イメージビルド時の環境情報(JSON形式)
  • manifest.json
    • これらのlayer/cofigに対してのリンクを持つindex(目次)

f:id:chataro00:20190705162845p:plain

f:id:chataro00:20190705174720p:plain

なお、layerのファイル群はUnion File System(UFS)という技術を使用して一つのディレクトリツリーにマージをしてマウントされます。

f:id:chataro00:20190705175431p:plain

Dockerの歴史とコンテナランタイム

製品としてのDockerの歴史とコンテナランタイムと呼ばれるdaemon/ライブラリ群に話を移します。 前述したように、Docker自体の構成も登場してから6年程度の中で大きく変わっています。

※画像は以下のスライドからの引用となります
37 Introducing Moby Project: a

f:id:chataro00:20190708104106p:plain

dotCloud社(後のDocker社)によりOSSとして公開、その後Moby Projectに移行され、 改修が進んでいく中で、次第にコンポーネントの分割が進み、現在の形となりました。

f:id:chataro00:20190708104132p:plain

このようなコンポーネント分割に至った経緯ですが、

  • 実装を分割してpluggabilityを向上したい
  • 相互運用性を担保するためにI/F仕様を標準化させたい
  • (k8sの登場を受け)kubeletからもコンテナのライフサイクル管理の機能を共通ライブラリとして利用できるようにしたい

などの複数の関係者の思惑やニーズがあったのでは?と推測します。
以下、各コンポーネントの解説です。

dockerd

dockerdは、CLI / REST API / docker daemonの3層構造に分かれており、
各コンポーネントは独立した別個の機能/役割を有します。

f:id:chataro00:20190708114550p:plain

OCI

containerd及びruncは、dockerd及びkubeletのバックエンドとして、 OCI(Open Container Initiative)に準拠する方式で、実際的なコンテナのライフサイクルの管理を行います。

f:id:chataro00:20190708115142p:plain

containerdがハイレベルのコンテナのオーケストレーションを、 runcがローレベルな実際のLinuxとの対話を担当します。

OCI Specでは、OCI Image Format SpecOCI Runtime Spec の2種が定められており、 Image SpecではContainer Registry上のコンテナイメージの配荷(distribution)の方法が、 Runtime Specではコンテナイメージを実際のLinuxのFilesystem上に展開する際の作法(Bundle)と コンテナの開始から終了に至るまでのライフサイクルがそれぞれ定義されています。

f:id:chataro00:20190708115301p:plain

f:id:chataro00:20190708115329p:plain

containerd

containerdは前述のように、ハイレベルなコンテナのライフサイクルを管理しています。
デーモンとして動作するよう実装されています。

f:id:chataro00:20190708105628p:plain

具体的には下図のように、Container RegistryからのImageの取得(Distribution)、取得したImageのFileSystem上での展開(Bundle)、 コンテナの実体の生成と以降のライフサイクル/コンテナのステートの管理(Runtime Controll)を管理しているようです。

f:id:chataro00:20190708105927p:plain

f:id:chataro00:20190708122024p:plain

runc

runcはLinuxと直接会話し、コンテナの実体としての各種Namespaceやcgroupなどの操作を行うローレベルなコンテナランタイムです。 libcontainer というライブラリを内部に持っており、そこからcgoというライブラリでC言語のコードを実行し、Systemcallを発行することでLinuxを操作します。

f:id:chataro00:20190708110210p:plain

f:id:chataro00:20190708110228p:plain

dockerを運用するためのツールたち

以上のようにDockerについて深掘りしていきましたが、仕組みを理解するのはいいけど、実際何がどう動いているか観測できないと物足りない!
ということで、最後にDockerの浅いところから深いところまで覗けるツールをざっくりご紹介します。

bpftraceのバックエンドに使われているeBPFは、11月に2冊の新刊が発売予定ということで、今後の盛り上がりが期待されますね!

参考:
Linux Observability With Bpf: Advanced Programming for Performance Analysis and Networking
https://www.amazon.co.jp/dp/1492050202

BPF Performance Tools
https://www.amazon.com/dp/0136554822

systemd-cgtop

f:id:chataro00:20190708122526p:plain

dockerpsns.sh

f:id:chataro00:20190708111436p:plain

perf event

f:id:chataro00:20190708111518p:plain

docker stats / ctop

f:id:chataro00:20190708111555p:plain

csysdig

f:id:chataro00:20190708111626p:plain

bpftrace

f:id:chataro00:20190708111732p:plain

strace

f:id:chataro00:20190708111820p:plain

実サーバー無しでLet's Encryptの証明書を発行する

はじめに

先進サービス開発事業部の山岡です。

先日所用で手元の端末にLet's Encryptの証明書を発行したのですが、これには公式推奨ツールであるcertbotを使う必要があります。

単純にインストールする方法の他にDockerを使って使い捨てることもでき、今回その方法を試したので手順を書きたいと思います(Dockerはインストール済みの前提です)。

続きを読む