はじめに
この記事はOracle Cloud Infrastructure #2 Advent Calendar 2020の24日目の記事です。
12/17-12/18の2日間に渡って開催されたOracle Developer Daysの2日目にCloud Nativeセッションを担当させて頂きました。
この記事では、上記セッションで使用した簡単なデモアプリケーションについて、その解説と補足をしていきたいと思います。
デモのソースコードについて
使用したデモについては、このレポジトリにありますので、ご自由にお使い頂ければと思います。
セッション資料はこちらです。
動画についてもこちらにアップロードされています。
デモアプリケーションの構成
上図はセッション資料にも含まれている図です。
Nuxt.jsで実装したフロントエンドアプリケーションとHelidon(後述)アプリケーションをGraalVM(後述)でNative Image化(後述)した2種類のバックエンドAPIから構築したアプリケーションです。全体がショボすぎてマイクロサービスなのかというのはありますが、一応サービスごとにコンポーネントが分かれているということで許してください...
フロントエンドへはロードバランサー経由でアクセスします。
フロントエンドからバックエンドAPIへのアクセスはClusterIPを利用します。
ここで、勘が良い方はお気づきかもしれませんが、フロントエンドはいわゆるSSR(サーバサイドレンダリング)で構築しています。
ランタイム環境はKubernetesを利用しています。(ここでは、Oracle Cloud Infrastructureで利用可能なマネージドKubernetesサービスを使っています)
デモアプリケーションで利用している技術スタック
このデモでは、Oracle Cloud Infrastructureで利用可能な以下のサービスやプロダクトを利用して実装しています。
-
GraalVM:オープンソースのランタイムエンジンです。JavaやScalaなどのJVM上で動作する言語以外にもJavaScriptやPythonなども動作させることがでできます。後ほど紹介しますが、Javaのバイトコードを事前にコンパイルするNative Imageという仕組みも利用することができます。今回のデモアプリケーションでは、HelidonアプリケージョンをNative Imageとしてビルドして稼働させています。また、今回は、Nuxt.jsで実装したフロントエンドアプリケーションもGraalVMで動作させています。
-
Helidon:非常に軽量なオープンソースのアプリケーションフレームワークです。マイクロサービス・アーキテクチャを導入する際に非常に相性が良いオープンソースのJavaのアプリケーション・フレームワークとなっています。今回のデモアプリケーションでは、バックエンドアプリケーションをHelidonで実装しています。
-
Oracle Container Engine for Kubernetes(OKE):Oracle Cloud Infrastructureで提供されているマネージドのKubernetesサービスです。今回のデモアプリケーションでは、アプリケーションのランタイム環境としてOKEを利用しています。
デモの構成
このデモは、以下の構成になっています。
.
├── devdays-demo-backend_kohaku
├── devdays-demo-backend_trendword
├── devdays-demo-frontend
├── k8s_manifest
それぞれのディレクトリについて見ていきます。
ソースコードまで見ていくとキリがないので、今回はDockerfileとmanifestを中心に見ていきます。
Dockerfile
まずはそれぞれのアプリケーションのDockerfileを見ていきます。
devdays-demo-backend_kohaku
# 1st stage, build the app
FROM helidon/jdk11-graalvm-maven:20.1.0 as build
今回はNative Imageとしてビルドするためにhelidon公式で公開されているベース・イメージを利用します。
このイメージには、HelidonをビルドするためのJDKの他にGraalVMとmavenコマンドが梱包されています。
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 -Pnative-image -Dnative.image.skip -Dmaven.test.skip -Declipselink.weave.skip
作業ディレクトリWORKDIR
を作成し、ローカルからコンテナにpom.xml
をコピーします。
その後、mavenコマンドでビルドしていきます。
ここでは、pom.xml
に定義されている内容を元にライブラリや依存関係などをローカルにキャッシュするためのプロセスです。
このプロセスを一度実行すると、以降のビルドではpom.xml
が更新されるまではこのプロセスはスキップされます。
また、実際にNative Imageはビルドされません。
# Do the Maven build!
# Incremental docker builds will resume here when you change sources
ADD src src
RUN mvn package -Pnative-image -Dnative.image.buildStatic -DskipTests
RUN echo "done!"
ここではじめてソースコードをローカルからコピーし、ビルドを行っていきます。
-Pnative-image -Dnative.image.buildStatic
オプションを指定することで、Native Imageとしてビルドされます。
遅くなりましたが、ここでNative Imageについて紹介しておきます。
上記で紹介したセッション資料にも記載している絵を利用して説明します。
Native Imageとは、Javaコード(JVMベースの言語)を事前コンパイルし、スタンドアローンで実行可能な形にコンパイルするしたものです。
具体的には
- 依存関係にあるアプリケーションクラス群
- 実行時に利用するJDKクラス群(ランタイム環境)
- 静的にリンクされたJDKのネイティブコード
などを含んでいます。
メリットとしては、
- ランタイム起動時間の短縮
- メモリフットプリントの極小化
- セキュリティの向上(ランタイム環境の隔離)
などがあげられます。
ビルドしたNative Imageはシングルバイナリとして実行することが可能です。
# 2nd stage, build the runtime image
FROM scratch
WORKDIR /helidon
# Copy the binary built in the 1st stage
COPY --from=build /helidon/target/backend-kohaku-app .
ENTRYPOINT ["./backend-kohaku-app"]
EXPOSE 8081
ここからはマルチステージビルドを利用して、非常に軽量なベース・イメージscratch
に先ほどビルドしたNative Imageをコピーします。
最後にNative Imageを実行します。
ランタイム環境も含めたバイナリなので、JDKがベース・イメージに不要ですし、java -jar
などのコマンドも付与することなく実行できます。
最後の行のEXPOSE 8081
はローカル環境で動作確認をするために付与したものです。
devdays-demo-backend_trendword
こちらもdevdays-demo-backend_kohaku
と全く同じDockerfileになっています。
# 1st stage, build the app
FROM helidon/jdk11-graalvm-maven:20.1.0 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 -Pnative-image -Dnative.image.skip -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 -Pnative-image -Dnative.image.buildStatic -DskipTests
RUN echo "done!"
# 2nd stage, build the runtime image
FROM scratch
WORKDIR /helidon
# Copy the binary built in the 1st stage
COPY --from=build /helidon/target/backend-trendword-app .
ENTRYPOINT ["./backend-trendword-app"]
異なるのは、Native Imageのファイル名だけになります。
devdays-demo-frontend
同様にフロントエンドアプリケーションもDockerfileを見ていきます。
FROM oracle/graalvm-ce:latest
# GraalVMのnpmバイナリをPATHに追加
ENV PATH $PATH:$JAVA_HOME/bin/npm
# ディレクトリ作成
WORKDIR /app
冒頭でお話しした通り、今回はNuxt.jsのアプリケーションをGraalVMで動作させてきます。
GraalVMにはgraaljsというECMAScript2020互換の仕組みが実装されているので、これを利用します。
ちなみにgraaljsはこちらに詳細があります。
ベース・イメージにGraalVM CE(Community Edition)を使用していますが、この中にnpmバイナリが梱包されています。
このnpmバイナリにパスを通すことで、以降のNuxt.jsアプリケーションのビルドに利用していきます。
# パッケージをコピー
COPY package*.json ./
# npm モジュールをインストール
RUN npm install --quiet
# 成果物コピー
COPY . .
# このコマンドをしないといけないとwarnが出たので
RUN npm rebuild
# 本当はいらないが開発環境でvue-cliを使っていたのでそこに含まれているパッケージを使っているようでwarnが出たので入れる
RUN npm install vue-cli -g
# ビルド
RUN npm run build
# なくても良い
ENV HOST 0.0.0.0
# なくても良い
EXPOSE 3000
# 起動
CMD ["npm", "run", "start"]
ここから先は、GraalVMに梱包されているnpmバイナリでビルドしていくだけです。
manifest
続いて、各アプリケーションのmanifestを見ていきます。
backend_kohaku.yaml
要所をピックアップしていきます。
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-kohaku-app
labels:
app: backend-kohaku
spec:
replicas: 2
selector:
matchLabels:
app: backend-kohaku
今回レプリカ数は2個で冗長化しています。
spec:
containers:
- name: backend-kohaku
image: phx.ocir.io/orasejapan/devdays2020/backend-kohaku-app
ports:
- containerPort: 8081
コンテナイメージは、Oracle Cloud Infrastructure Registry(OCIR)に登録しています。
アプリケーションは8081ポートで公開します。
- name: h2kohaku
image: oscarfonts/h2
env:
- name: H2_OPTIONS
value: "-ifNotExists"
ports:
- containerPort: 81
- containerPort: 1521
今回は、サイドカーとしてもう一つのコンテナをデータソース(データベース)として利用します。
サイドカーパターンについてはこちらを参考にしてください。
サイドカーに対してはlocalhostでアクセスできます。
次にアプリケーションに対するServiceの設定です。
フロントエンドのサービスからバックエンドアプリケーションをルーティングするための設定です。
apiVersion: v1
kind: Service
metadata:
name: backend-kohaku-svc
spec:
type: ClusterIP
ports:
- port: 8081
targetPort: 8081
selector:
app: backend-kohaku
ポートについては、アプリケーション側で利用している8081ポートをそのまま流しています。
今回はClusterIPタイプを利用しています。
今回はフロントエンドをSSR(サーバサイドレンダリング)で実装しているので、バックエンドをサーバサイド(ブラウザではなく)で呼び出しているので、ClusterIPとしました。
backend_trendword.yaml
ほぼbackend_kohaku.yaml
と同様です。異なるのはポート番号のみです。
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-trendword-app
labels:
app: backend-trendword
spec:
replicas: 2
selector:
matchLabels:
app: backend-trendword
template:
metadata:
labels:
app: backend-trendword
spec:
containers:
- name: backend-trendword
image: phx.ocir.io/orasejapan/devdays2020/backend-trendword-app
ports:
- containerPort: 8082
- name: h2trendword
image: oscarfonts/h2
env:
- name: H2_OPTIONS
value: "-ifNotExists"
ports:
- containerPort: 81
- containerPort: 1521
---
apiVersion: v1
kind: Service
metadata:
name: backend-trendword-svc
spec:
type: ClusterIP
ports:
- port: 8082
targetPort: 8082
selector:
app: backend-trendword
frontend_app.yaml
最後にフロントエンドアプリケーションのmanifestを見ていきたいと思います。
こちらも要所要所で解説していきます。
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-app
labels:
app: frontend
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
フロントエンドアプリケーションも、バックエンドアプリケーションと同様に2個のレプリカで冗長化しています。
spec:
containers:
- name: frontend
image: phx.ocir.io/orasejapan/devdays2020/frontend-app
ports:
- containerPort: 3000
env:
- name: HOST
value: "0.0.0.0"
コンテナイメージはバックエンド同様にOCIRにpushしています。
コンテナポートは3000ポートで公開しています。
apiVersion: v1
kind: Service
metadata:
name: frontend
spec:
type: LoadBalancer
ports:
- port: 3000
selector:
app: frontend
こちらはインターネット側からのルーティングとしてLoadBalancerを利用しています。
ポート番号はアプリケーションと同様に3000番ポートで流しています。
以上がmanifestの紹介になります。
まとめ
今回はOracle Cloud Infrastructureで利用できる各種テクノロジーでマイクロサービスを実装してみました。
もちろん、マイクロサービス自体は今回解説したものだけで簡単に実装できてしまうものではありません。
ただ、今回利用したような技術スタックを利用していけば、少しでも効率的に実装や運用を行うことができるのではないかと思います。
最後に...
今回ご紹介したGraalVM(Community Edition)とHelidonはオープンソースで公開されています。
積極的に開発が進められているプロダクトになりますので、ぜひお試しいただければと思います!
ちなみにGraalVM Enterprise EditionはOracle Cloud Infrastructureで無償でご利用いただけます!