2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

OKEでARMコンテナを動かす

Last updated at Posted at 2021-07-25

始めに

5 月の終わりごろから 6 月に掛けて、Twitter などでちょっと話題になっていた Oracle Cloud Infrastructure(OCI) のニュースを覚えていますでしょうか?

image01.png

https://www.oracle.com/jp/corporate/pressrelease/jp20210526.html より引用

上にもあるように、ARM ベースのインスタンスが Always Freeで合計 4 コア、24GB メモリまで永久に無償で使えるというものです。現在、東京リージョンはリソースがひっ迫しているので、新しくアカウントを作成する方はホームリージョンを大阪や Ashburn にすることをお勧めします。(Ashburn リージョンでは普通に作れました) また、それに関連してOracle Container Engine for Kubernetes(OKE)の Arm インスタンスのサポートというブログも公開されています。
そこで今回は、そんな ARM ベースの Ampere A1Compute と AMD ベースのインスタンスで OKE1の Node Pool を構築し、実際にコンテナアプリケーションが動作するところまでを確認してみたいと思います。

環境のゴール

最終的に以下のような環境を作成します。

architecture.png

OKE で管理する Node Pool を、ARM ベースの Ampere A1Compute と AMD ベースのインスタンスで構築しそれらが混在するような構成とします。

手順

マルチアーキテクチャサポート

Docker Hub を使っている方にとってはお馴染みかと思いますが、コンテナレジストリで複数の CPU アーキテクチャの Docker イメージを格納しておくと、イメージの取得時に OS と CPU アーキテクチャに対応するイメージを自動的(もしくは手動)に取得してくれる機能です。今回は、Helidon2 で実装したコンテナアプリケーションをマルチ CPU アーキテクチャに対応させたいと思います。

まずは、アプリケーションを作成します。今回は、単純に"Hello World"と返すシンプルな API を作ります。

helidon init --flavor MP --archetype bare --groupid com.example --package com.example.greet --name greet

実行すると以下のようなファイル等が生成されます。

C:.
│  .helidon
│  pom.xml
│  README.md
│
└─src
    ├─main
    │  ├─java
    │  │  └─com
    │  │      └─example
    │  │          └─greet
    │  │                  GreetResource.java
    │  │                  package-info.java
    │  │
    │  └─resources
    │      │  logging.properties
    │      │
    │      └─META-INF
    │          │  beans.xml
    │          │  microprofile-config.properties
    │          │
    │          └─native-image
    │                  reflect-config.json
    │
    └─test
        └─java
            └─com
                └─example
                    └─greet
                            MainTest.java

まずは、ローカルで実行してみます。

# build the application
helidon build
# run the application
java -jar .\target\greet.jar
2021.07.20 11:52:25 情報 io.helidon.common.LogConfig Thread[main,5,main]: Logging at initialization configured using classpath: /logging.properties
2021.07.20 11:52:27 情報 io.helidon.tracing.tracerresolver.TracerResolverBuilder Thread[main,5,main]: TracerResolver not configured, tracing is disabled
2021.07.20 11:52:27 警告 io.helidon.microprofile.tracing.TracingCdiExtension Thread[main,5,main]: helidon-microprofile-tracing is
on the classpath, yet there is no tracer implementation library. Tracing uses a no-op tracer. As a result, no tracing will be configured for WebServer and JAX-RS
2021.07.20 11:52:27 情報 io.helidon.microprofile.security.SecurityCdiExtension Thread[main,5,main]: Authentication provider is missing from security configuration, but security extension for microprofile is enabled (requires providers configuration at key security.providers). Security will not have any valid authentication provider
2021.07.20 11:52:27 情報 io.helidon.microprofile.security.SecurityCdiExtension Thread[main,5,main]: Authorization provider is missing from security configuration, but security extension for microprofile is enabled (requires providers configuration at key security.providers). ABAC provider is configured for authorization.
2021.07.20 11:52:27 情報 io.helidon.microprofile.server.ServerCdiExtension Thread[main,5,main]: Registering JAX-RS Application: HelidonMP
2021.07.20 11:52:28 情報 io.helidon.webserver.NettyWebServer Thread[nioEventLoopGroup-2-1,10,main]: Channel '@default' started: [id: 0x24ea6c3f, L:/[0:0:0:0:0:0:0:0]:8080]
2021.07.20 11:52:28 情報 io.helidon.microprofile.server.ServerCdiExtension Thread[main,5,main]: Server started on http://localhost:8080 (and all other host addresses) in 2412 milliseconds (since JVM startup).
2021.07.20 11:52:28 情報 io.helidon.common.HelidonFeatures Thread[features-thread,5,main]: Helidon MP 2.3.2 features: [CDI, Config, Fault Tolerance, Health, JAX-RS, Metrics, Open API, REST Client, Security, Server, Tracing]
2021.07.20 11:52:36 情報 io.helidon.webserver.NettyWebServer Thread[nioEventLoopGroup-2-1,10,main]: Channel '@default' closed: [id: 0x24ea6c3f, L:/[0:0:0:0:0:0:0:0]:8080]
2021.07.20 11:52:36 情報 io.helidon.microprofile.server.ServerCdiExtension Thread[helidon-cdi-shutdown-hook,5,main]: Server stopped in 12 milliseconds.

公開されているエンドポイントを叩いてみます。

curl http://localhost:8080/greet
{"message":"Hello World!"}

次に、コンテナアプリケーションとして動作させるために Dockerfile を以下のように作成します。(マルチステージビルドを使用してコンテナイメージを作成しています)

# 1st stage, build the app
FROM maven:3.6-jdk-11 as build

WORKDIR /helidon

# Create a first layer to cache the "Maven World" in the local repository.
# Incremental docker builds will always resume after that, unless you update
# the pom
ADD pom.xml .
RUN mvn package -Dmaven.test.skip -Declipselink.weave.skip

# Do the Maven build!
# Incremental docker builds will resume here when you change sources
ADD src src
RUN mvn package -DskipTests

RUN echo "done!"

# 2nd stage, build the runtime image
FROM openjdk:11-jre-slim
WORKDIR /helidon

# Copy the binary built in the 1st stage
COPY --from=build /helidon/target/greet.jar ./
COPY --from=build /helidon/target/libs ./libs

CMD ["java", "-jar", "greet.jar"]

EXPOSE 8080

次に Docker Buildx を使用してマルチプラットフォームのイメージビルドを行います。今回は、AMD, ARM の両方に対応させるためそのためのイメージをビルドします。

Docker Hub

Docker イメージのビルド時に Docker Hub に対して自動的にイメージの push を行いたいので、あらかじめログインしておきます。

# login docker hub
docker login
Authenticating with existing credentials...
Login Succeeded

マルチプラットフォーム(linux/amd64, linux/arm64)のビルドを行います。

# create a container for building with linux/ARM64
docker buildx create --use --name ARM-build --platform linux/ARM64,linux/amd64
# build a container
docker buildx build --platform linux/amd64,linux/ARM64 . --push
# push a docker image

Docker Hub を確認してみると、確かに作成した Docker イメージに対して複数アーキテクチャがサポートされていることが分かります。

image03.png

これで、AMD, ARM のインスタンスに対して、OS/アーキテクチャを判断し自動的に適切なイメージを取得することができるようになります。

OCIR3

実際の開発では、CI/CD パイプラインの中で Docker イメージをビルドするケースが多いと思うのでそっちのパターンにも簡単に触れておきます。今回は、コンテナレジストリで OCIR を使用しますが Docker Hub でも同じです。以下、ざっくりとした全体構成です。

image04.png

  1. ソースコードの変更をリポジトリ(GitHub)に対して push する
  2. ソースコードの変更を検知し、CI/CD パイプラインが実行される
  3. コンテナイメージのビルド、コンテナレジストリ(OCIR)への push
  4. 更新した Docker イメージのタグを Kubernetes の Manifest ファイルに反映(Kustomize 使用)
  5. Manifest ファイルの変更を検知し、変更があった場合は Manifest ファイルを取得する
  6. Manifest の変更を Kubernetes クラスタへ反映する

まずはこれを実現するための、GitHub Actions の設定ファイルを以下のように書きます。

.github/workflows/docker-image.yaml
name: Docker Image CI

on:
  push:
    branches: [main]

jobs:
  buildx:
    name: build
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Login to OCIR # ... 1
        uses: docker/login-action@v1
        with:
          registry: nrt.ocir.io
          username: ${{ secrets.OCIR_USERNAME }}
          password: ${{ secrets.OCIR_PASSWORD }}

      - name: Set up Docker Buildx # ... 2
        uses: docker/setup-buildx-action@v1
        id: buildx

      - name: Build Docker image and push to OCIR # ... 3
        run: |
          docker buildx build -t ${{ secrets.OCIR_BASE_PATH }}/greet:$GITHUB_SHA ./greet --platform linux/amd64,linux/arm64 --push

  deploy:
    name: deploy
    runs-on: ubuntu-latest
    needs: buildx

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Setup Kustomize # ... 4
        uses: imranismail/setup-kustomize@v1

      - name: Update Kubernetes resources # ... 5
        run: |
          cd deploy/kubernetes/overlays
          kustomize edit set image ${{ secrets.OCIR_BASE_PATH }}/greet=${{ secrets.OCIR_BASE_PATH }}/greet:$GITHUB_SHA
          cat kustomization.yaml

      - name: Commit files # ... 6
        run: |
          git config --local user.email "shukawam@gmail.com"
          git config --local user.name "shukawam"
          git commit -am "Bump docker tag"

      - name: Push changes # ... 7
        uses: ad-m/github-push-action@master
        with:
          github_token: ${{ secrets.PERSONAL_TOKEN }}

簡単に解説していきます。

1. Login to OCIR

GitHub Actions から OCIR に対してイメージを push するためにログインしておきます。この時、OCIR にログインするために必要な資格情報等はリポジトリの secrets に定義しています。

image06.png

2. Set up Docker Buildx

Docker のリポジトリから公開されているBuildx のセットアップ用の Actionを使っています。本例以外にもwithパラメータで色々設定ができます。

Name Type Description
version String buildx version. (eg. v0.3.0, latest, https://github.com/docker/buildx.git#master)
driver String Sets the builder driver to be used (default docker-container)
driver-opts CSV List of additional driver-specific options (eg. image=moby/buildkit:master)
buildkitd-flags String Flags for buildkitd daemon (since buildx v0.3.0)
install Bool Sets up docker build command as an alias to docker buildx (default false)
use Bool Switch to this builder instance (default true)
endpoint String Optional address for docker socket or context from docker context ls
config String BuildKit config file

https://github.com/docker/setup-buildx-actionより引用

3. Build Docker image and push

Docker Buildxを使ってマルチアーキテクチャ(AMD, ARM)対応するための Docker イメージを作成し OCIR に対して push しています。また、作成したイメージに付与するタグが衝突しないように簡易的にコミット SHA をイメージのタグに設定しています。

4. Setup Kustomize

環境差分(と言っても今回は 1 環境しかないですが...)を管理するために Kustomize を使っています。使い慣れているので、Kustomize を使っていますが、Helm とかでも良いと思います。

5. Update Kubernetes resource

"Build Docker image and push" で作成した Docker イメージのタグで Manifest の内容を上書きしています。

6. Commit files & 7. Push changes

GitHub Actions 中で行われた変更を Manifest が保管されているリポジトリ(今回はソースコードが管理されているリポジトリと一緒)に反映しています。

(おまけ)ちゃんとやるなら

今回は簡易的に実施するため、ソースコード用のリポジトリと Manifest ファイル用のリポジトリを同一にしていますが、本来であれば以下のような構成が望ましいと思います。

image05.png

また、イメージスキャンやポリシーチェック等も今回は実施していないので、その辺りまできちんと実施したい場合は Oracle Cloud Hangout Cafe - CI/CD 最新事情 が非常に参考になると思います。

OKE で ARM コンテナを動かす

まずは、OKE の Node Pool を追加します。新しくクラスタを作成する方は、検証目的ならクイック作成がおすすめです。

image08.png

シェイプを VM.Standard.A1.Flex にする以外は自由に入力してください。

image09.png

これで、1 つの OKE クラスタに

  • AMD ベースの Node Pool
  • ARM ベースの Node Pool

が混在するような構成を作ることができました。さっそくこのクラスタに対して先ほどの CI/CD パイプライン構築の続きをしていきます。

Argo CD on OKE

Argo CD 自体の環境は、ArgoCD - Getting Started に沿って実施してください。と、言いたいところですが、現在 Getting Started から公開されている手順とは若干異なるので、少し言及しておきます。(関連 Issue は https://github.com/argoproj/argo-cd/issues/6787 です)

# create namespace "argocd"
kubectl create namespace argocd
# apply manifest
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

次に、Getting Started では初期パスワードが argocd-initial-admin-secret という secret に保存されているのでそれを参照してログインを行うのですが、私の環境(Argo CD v2.0.4)では、

kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
Error from server (NotFound): secrets "argocd-initial-admin-secret" not found

と、そんな secret ないよとエラーが出力されてしまったので、admin ユーザーのパスワードを変更することで対応しました。(FAQ より)

kubectl -n argocd patch secret argocd-secret \
> -p '{"stringData": {"admin.password": "$2a$10$qo3Q/Zt7UaYZgcapXCZ8jOdcnU1Z/PXe8Y4EG.JHiLZUCoBVQ9sFq", "admin.passwordMtime": "'$(date +%FT%T%Z)'"}}'
secret/argocd-secret patched

なお、パスワードは bcrypt でハッシュ化する必要があるので、この辺りを使うと楽にできます。

ここからアプリケーションの作成をします。まずは、リポジトリの定義をします。(作成したリポジトリの URL を指定します)

image10.png

次にアプリケーションの定義です。

image11.png

image12.png

image13.png

尚、今回は以下のような Manifest ファイルを用意しました。

AMD 用:

greet-amd.yaml
apiVersion: v1
kind: Service
metadata:
  name: greet-amd
  annotations:
    service.beta.kubernetes.io/oci-load-balancer-shape: 10Mbps
spec:
  selector:
    app: greet-amd
  ports:
    - name: http
      port: 8080
      targetPort: 8080
  type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: greet-amd
spec:
  replicas: 1
  selector:
    matchLabels:
      app: greet-amd
  template:
    metadata:
      labels:
        app: greet-amd
    spec:
      containers:
        - name: greet-amd
          image: nrt.ocir.io/orasejapan/shukawam/greet
          ports:
            - name: api
              containerPort: 8080
          readinessProbe:
            httpGet:
              path: /health/ready
              port: api
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /health/live
              port: api
            initialDelaySeconds: 15
            periodSeconds: 20
          imagePullPolicy: Always
      imagePullSecrets:
        - name: greet-secret
      nodeSelector:
        beta.kubernetes.io/arch: amd64

ARM 用:

greet-arm.yaml
apiVersion: v1
kind: Service
metadata:
  name: greet-arm
  annotations:
    service.beta.kubernetes.io/oci-load-balancer-shape: 10Mbps
spec:
  selector:
    app: greet-arm
  ports:
    - name: http
      port: 8080
      targetPort: 8080
  type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: greet-arm
spec:
  replicas: 1
  selector:
    matchLabels:
      app: greet-arm
  template:
    metadata:
      labels:
        app: greet-arm
    spec:
      containers:
        - name: greet-arm
          image: nrt.ocir.io/orasejapan/shukawam/greet
          ports:
            - name: api
              containerPort: 8080
          readinessProbe:
            httpGet:
              path: /health/ready
              port: api
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /health/live
              port: api
            initialDelaySeconds: 15
            periodSeconds: 20
          imagePullPolicy: Always
      imagePullSecrets:
        - name: greet-secret
      nodeSelector:
        beta.kubernetes.io/arch: arm64

nodeSelector で CPU アーキテクチャを選択しているのがポイントです。(今回の Docker イメージは ARM/AMD に両方対応しているので、nodeSelector で指定しなくても良しなに分散配置してくれるはずです。多分...)
tag の情報などは、Kustomize を使って各環境用に書き換えています。その辺りは、リポジトリをご参照ください。

OKE on ARM (and AMD)

実際にデプロイされた Pod を見てみましょう。

kubectl get pods,service -n multi-arch
NAME                             READY   STATUS    RESTARTS   AGE
pod/greet-amd-5749f798fd-9vfmt   1/1     Running   0          25m
pod/greet-arm-667d4b5946-pq4pk   1/1     Running   0          25m

NAME                TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)          AGE
service/greet-amd   LoadBalancer   10.96.222.236   129.159.116.65   8080:31041/TCP   60m
service/greet-arm   LoadBalancer   10.96.243.175   152.70.206.253   8080:30013/TCP   60m

無事、Pod が二つデプロイされています。それぞれの詳細を見てみましょう。まずは、作成した OKE のクラスターに含まれる Node の情報を見てみます。

kubectl get nodes
NAME          STATUS   ROLES   AGE   VERSION
10.0.10.12    Ready    node    13h   v1.20.8
10.0.10.165   Ready    node    15h   v1.20.8

さて、この Node ですが、OCI コンソールから確認してみると確かに AMD と ARM のインスタンスから構成されている事が分かります。(10.0.10.12: AMD, 10.0.10.165: ARM)

image14.png

デプロイされた Pod の詳細をそれぞれ見てみます。

kubectl describe pod greet-amd-5749f798fd-9vfmt -n multi-arch
Name:         greet-amd-5749f798fd-9vfmt
Namespace:    multi-arch
Priority:     0
Node:         10.0.10.12/10.0.10.12
Start Time:   Sun, 25 Jul 2021 06:26:39 +0000
Labels:       app=greet-amd
              pod-template-hash=5749f798fd
Annotations:  <none>
Status:       Running
IP:           10.244.1.17
IPs:
  IP:           10.244.1.17
# ... 省略

Node: 10.0.10.12/10.0.10.12 から確かに、AMD の Node に配置されている事が分かります。次に、もう片方の Pod の詳細を見てみます。

kubectl describe pod greet-arm-667d4b5946-pq4pk -n multi-arch
Name:         greet-arm-667d4b5946-pq4pk
Namespace:    multi-arch
Priority:     0
Node:         10.0.10.165/10.0.10.165
Start Time:   Sun, 25 Jul 2021 06:26:39 +0000
Labels:       app=greet-arm
              pod-template-hash=667d4b5946
Annotations:  <none>
Status:       Running
IP:           10.244.0.26
IPs:
  IP:           10.244.0.26
# ... 省略

こちらも Node: 10.0.10.165/10.0.10.165 から確かに、ARM の Node に配置されている事が分かります。

最後に、それぞれ Load Balancer を通して公開されているエンドポイントを叩いてみます。

AMD:

curl http://129.159.116.65:8080/greet
{"message":"Hello World!"}

ARM:

curl http://152.70.206.253:8080/greet
{"message":"Hello World!"}

終わりに

想像しているよりも簡単にヘテロジニアスな環境を作ることが出来ました。ARM インスタンスはかなり安い料金設定(コア時間当たり 1.2 円)なのでうまく活用してコストダウンを図りたいですね!

また、今回作成した一連のファイルは、リポジトリに格納しているので、良ければ参考にしてください。

参考

  1. Oracle Container Engine for Kubernetes: OCI が提供するマネージド Kubernetes サービス

  2. Java ベースの軽量フレームワーク

  3. Oracle Cloud Infrastructure Registry: OCI が提供する Docker v2 対応のコンテナレジストリサービス

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?