概要
一般に、Kubernetes 環境にカバレッジ エージェントをデプロイする場合は、次の 3 つの点を考慮する必要があります。
- カバレッジ エージェントをテスト対象アプリケーション (AUT) のコンテナに挿入します。
- コンテナの起動時にカバレッジ エージェントを呼び出します。
- Kubernetes クラスターの内部または外部からカバレッジ エージェントにアクセスします。
このページでは、Spring Petclinic プロジェクトを AUT として使用した例を挙げながら、これらの考慮事項について広範に説明します。GitHub (https://github.com/parasoft/spring-petclinic-microservices) からプロジェクトをダウンロードして、従うことをお勧めします。
このドキュメントでは Java エージェントが使用されますが、プロセスは dotNet の使用例でも同様である必要があります。
カバレッジ エージェントのインストール
ランタイム カバレッジを収集するために必要なコンポーネントを AUT のコンテナに挿入する必要があります。Java の場合、これは agent.jar とそのプロパティで構成され、エージェントを起動するコマンドに直接挿入するか、プロパティ ファイルを使用して実行することができます。カスタムイメージを使用している場合、これはイメージのビルド時に行うことができます。そうでない場合は、ボリュームを使用して必要なファイルをコンテナにマウントできます。それには複数の方法があります。たとえば、agent.jar ファイルを既に含むイメージを initContainer として使用し、共有 emptyDir ボリュームと組み合わせてプライマリ コンテナに jar へのアクセスを許可する方法や、agent.jar とそのプロパティ ファイルを既に含むボリュームを直接マウントする方法などです。以下の例では、これらの両方の方法を示しています。
Kubernetes ボリュームの詳細については、Kubernetes のドキュメント (https://kubernetes.io/docs/concepts/storage/volumes/) を参照してください。
initContainer メソッド
このメソッドでは、agent.jar ファイルを含むイメージを initContainer として使用し、共有 emptyDir ボリュームと組み合わせて、プライマリ コンテナに jar へのアクセスを許可します。Spring Petclinic プロジェクトを使用した API Gateway の YAML ファイルの例を以下に示します。
spec:
template:
spec:
containers:
- name: api-gateway
image: <HOST>/ctp/springcommunity/spring-petclinic-api-gateway:latest
volumeMounts:
- name: api-gateway-volume
mountPath: /tmp/coverage
initContainers:
- name: java-coverage-agent
image: <HOST>/ctp/java-coverage-init-container:latest
command: ['sh', '-c', 'cp /agent/agent.jar /tmp/coverage/agent.jar']
volumeMounts:
- name: api-gateway-volume
mountPath: /tmp/coverage
volumes:
- name: api-gateway-volume
emptyDir: {}
initContainer イメージ内のカバレッジエージェントを共有 emptyDir ボリュームにコピーするコマンドに注目してください。
ボリュームを直接マウントする方法
この方法では、agent.jar ファイルと agent.properties ファイルがすでに含まれているボリュームを直接マウントします。
これを行うために選択できるボリュームには、いくつかのタイプがあります。以下の例では、NFS ボリュームを使用しています。これは、1 種類のクラウドプロバイダーに縛られず、hostPath ボリュームの場合のようにクラスターのホストストレージにアクセスする機能を必要としないからです。ユーザーは、使用する環境に適したボリューム タイプを使用できます。
異なるコンテナに対応するカバレッジエージェントを持つ複数のサービスがあり、それぞれが独自のポッドとデプロイメントにある場合、それぞれに対応する PersistentVolume と PersistentVolumeClaim を作成する必要があります。Spring Petclinic 用の API Gateway (異なるコンテナに 4 つのサービスがある) の YAML ファイルの例を以下に示します。
# ==== Persistent Volume to Mount Agent Jar and Properties File into API Gateway ====
apiVersion: v1
kind: PersistentVolume
metadata:
name: api-gateway-volume
spec:
capacity:
storage: 1Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
path: <PATH ON NFS MOUNT>
server: <ADDRESS OF NFS SERVER>
---
# ==== PersistentVolumeClaim for API Gateway Mount ====
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: api-gateway-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: nfs
resources:
requests:
storage: 1Gi
volumeName: "api-gateway-volume"
agent.jar ファイルと agent.properties ファイルが NFS サーバーの <PATH ON NFS MOUNT> 内にあることを確認してください。各 agent.properties ファイルは、マイクロ サービス カバレッジ ワークフローの一部として、個別の DTP プロジェクトの仕様を含むため、<PATH ON NFS MOUNT> で指定された個別のディレクトリを各ボリュームに使用する必要があります。ボリュームは、以下の例のようにコンテナに関連付けることができます。
spec:
template:
spec:
containers:
- volumeMounts:
- name: api-gateway-volume
mountPath: <MOUNT PATH INSIDE CONTAINER>
volumes:
- name: api-gateway-volume
persistentVolumeClaim:
claimName: api-gateway-pvc
コンテナが作成されると、agent.jar と agent.properties は <MOUNT PATH INSIDE CONTAINER> にあります。
カバレッジ エージェントの起動
カバレッジ エージェントをどのように起動するかは、カバレッジ エージェントのインストール方法によって異なります。たとえば、カバレッジ エージェントを含むカスタム イメージを作成した場合、Docker ファイルの ENTRYPOINT フィールドまたは CMD フィールドを設定するか、後述する環境変数のいずれかを使用することで、エージェントを起動できます。ボリュームを使用してファイルをコンテナにマウントした場合、どのメソッドを使用したかによって方法が異なります。
initContainer メソッドでは、カバレッジ エージェントを起動するだけでなく、エージェント プロパティを導入する必要があります。前者については、別の共有ボリュームで ConfigMap を使用して agent.properties ファイルを挿入し、それを参照することもできますし、エージェントを起動する javaagent 引数の一部として必要なプロパティを直接設定することもできます。以下の例では、<JAVA_TOOL_OPTIONS> 環境変数 (詳細については このページ を参照) を使用していますが、<JAVA_TOOL_OPTIONS> (詳細については このページ を参照) を使用することもできますし、NFS ボリューム ベースの方法で説明したように、Java 呼び出しを直接変更することもできます。
spec:
template:
spec:
containers:
- name: api-gateway
image: <HOST>/ctp/springcommunity/spring-petclinic-api-gateway:latest
command: ["./dockerize"]
args: ["-wait=tcp://discovery-server:8761", "-timeout=60s", "--", "java", "org.springframework.boot.loader.JarLauncher"]
env:
- name: JDK_JAVA_OPTIONS
value: "-javaagent:/tmp/coverage/agent.jar=settings=/config/agent.properties"
volumeMounts:
- name: api-gateway-configmap
mountPath: /config
volumes:
- name: api-gateway-configmap
configMap:
name: api-gateway-configmap
items:
- key: "agent.properties"
path: "agent.properties"
---
apiVersion: v1
kind: ConfigMap
metadata:
name: api-gateway-configmap
data:
agent.properties: |
jtest.agent.runtimeData=runtime_coverage
jtest.agent.includes=org/springframework/samples/**
jtest.agent.autostart=false
jtest.agent.port=8050
この NFS ボリュームベースの方法により、コンテナがカバレッジ エージェントにマウントされるように設定したら、コンテナ仕様の command フィールドと args フィールドを組み合わせて使用することで、コンテナのエントリ ポイントをオーバーライドし、カバレッジ エージェントを呼び出すことができます。正確な仕組みはコンテナによって異なりますが、Java の場合は一般的に、テスト対象アプリケーションの jar ファイルや war ファイルを呼び出すときに、war ファイルにカバレッジ エージェントの引数を追加する必要があります。
Spring Petclinic の例では、イメージは Dockerize を使用して開始順を調整します。そのため、以下のように javaagent 引数を環境変数として追加して渡す必要があります。
spec:
template:
spec:
containers:
- command: ["./dockerize"]
args: ["-wait=tcp://discovery-server:8761", "-timeout=60s", "--", "java", "$(COV_AGENT_ARGS)", "org.springframework.boot.loader.JarLauncher"]
ports:
- containerPort: <COVERAGE AGENT PORT>
env:
- name: COV_AGENT_ARGS
value: "-javaagent:<PATH TO AGENT.JAR INSIDE CONTAINER>=settings=<PATH TO AGENT.PROPERTIES INSIDE CONTAINER>,runtimeData=<PATH TO RUNTIME DATA DIRECTORY IN CONTAINER>"
また、イメージ自体でアプリケーションに指定しなければならない containerPort に加えて、カバレッジ エージェントのポートも公開する必要があることに注意してください。ポート 8050 がデフォルトですが、これは agent.properties ファイルを使用して必要に応じて変更できます。
カバレッジ エージェントへのアクセス
カバレッジ エージェントにアクセスする際には、Kubernetes クラスタ内からアクセスするのか、Kubernetes クラスタ外からアクセスするのかという点を考慮する必要があります。以下では両方のケースを取り上げます。
Kubernetes クラスタ内でのアクセス
コンテナがポッドを共有しない限り (その場合は localhost を使って通信可能)、Kubernetes での通信にはサービスを利用する必要があります。ポッド A 上のコンテナ A は、クラスタ内の IP 経由でポッド B 上のコンテナ B にアクセスできますが、ポッド B が再起動されたり、新しい IP でコンテナ B を実行する別のポッドに置き換えられたりする可能性があるため、これは良くないプラクティスです。
サービスの利点は、既知のポッド上のコンテナ B を直接ポイントしてコンテナ A を必要な機能に直接接続する代わりに、コンテナ B がどこにあるかの詳細を知る必要がないことです。コンテナ A は、コンテナ B にアクセスするために、一種のプロキシとしてサービスを参照できます。これには、共有ネットワーク上の Docker コンテナが名前で相互参照できるのと非常によく似ており、特定のポッド IP ではなくサービスの名前で必要な機能を参照できるという利点もあります。
デフォルトのサービスのタイプは ClusterIP であり、Kubernetes クラスタ内のコンポーネント間の通信というこの目的を果たします。この接続を提供するには、カバレッジ エージェントごとに 1 つのサービスを作成します。サービスが定義されている API Gateway のカバレッジ エージェントの例を以下に示します。
apiVersion: v1
kind: Service
metadata:
name: api-gateway-coverage-agent
spec:
ports:
- port: <SERVICE ACCESS PORT>
protocol: TCP
targetPort: <COVERAGE AGENT CONTAINERPORT>
selector:
app: petclinic
microservice: api-gateway
サービスに関する詳細なドキュメントは、https://kubernetes.io/docs/concepts/services-networking/service/ にあります。
クラスタ内からカバレッジ エージェントにアクセスするだけであれば、これで十分です。たとえば、クラスタ内のカバレッジを調整するように CTP を設定している場合は、CTP でカバレッジ エージェントの URL を http|https://<SERVICE NAME>:<SERVICE ACCESS PORT> (上記の例では、http://api-gateway-coverage-agent:<SERVICE ACCESS PORT>) として設定するだけで、CTP から API Gatewayのカバレッジ エージェントにアクセスできるようになります。
Kubernetes クラスタ外からのアクセス
クラスタの外部からカバレッジ エージェントなどのリソースにアクセスする必要がある場合、状況はさらに複雑になります。ClusterIP サービスは通常、クラスタ内からしかアクセスできないため、カバレッジ エージェントにアクセスする他の方法を検討する必要があります。次のような方法があります。
- ポート転送: kubectl で port-forward コマンドを使用すると、Kubernetes クラスタのホストとポッド上の特定のポートとの間の接続を確立することができます。このソリューションは拡張性が非常に低く、ホストとポッドを直接リンクしているため、特定のリソースへのアクセスが、接続が確立された時点で実行されている特定のポッドと密結合するという問題も発生します。そのため、この方法は開発やテスト以外の用途には適していません。
- 既存の ClusterIP サービスへのプロキシ アクセス: kubectl プロキシを使用して ClusterIP サービスにアクセスできますが (詳細については、このページ を参照)、これには Kubernetes API サーバーとの接続が含まれるほか、いくつかの制限/注意事項があるため、クラスタ管理者による開発やテスト以外の用途には適していません。
- NodePort サービス: NodePort サービスとは、
--service-node-port-rangeで指定された範囲 (デフォルトでは 30000 ~ 32767) に、さらに 3 つ目のnodePort (通常は自動割り当て) を持つサービスのことです。<NODE IP ADDRESS>:<nodePort VALUE>を使用して、クラスタの外部から NodePort サービスにアクセスできます。 - ロードバランサー サービス: ロードバランサー サービスは、外部トラフィックをクラスタにルーティングするために、Kubernetes クラスタの外部にあるコンポーネント (通常は AWS や GKE のようなクラウド プロバイダによって提供される有料の機能) に依存します。
- Ingress: Ingress はクラスタへの一種のエントリーポイントとして機能し、他の機能の中でも特に複数のサービスへのパスベースのルーティングを可能にします。Ingress コントローラーをクラスタにインストールする必要があります。詳細については このページ を参照してください。
ご覧のとおり、さまざまなオプションから選択できます。Kubernetes クラスタのタイプがおそらく最も重要な要素です。AWS でホストされているクラスタにデプロイする場合は、LoadBalancer が適切な選択でしょう。クラスタがベアメタルの場合、NodePort がより魅力的になります。
Spring Petclinic の例では Ingress を使用しています。Ingress には、ここで考慮すべき 2 つの優れた利点があります。
- パスベースのルーティングを使用すると、特定のサービスのエンドポイントと、そのサービスで実行されているカバレッジ エージェントへのアクセスとの間の接続を簡単に示すことができます。たとえば、アプリケーション foobar にアクセスするエンドポイントのホスト名として foo.bar を使用すると、foobar コンテナで実行されているエージェントのエンドポイントに foo.bar/agent というサブパスを使用できます。
- Ingress は、Helm チャートや YAML ファイルの移植性を高めます。Ingress コントローラが動作するクラスタであれば、変更やカスタマイズをほとんど行うことなくデプロイできるはずです。
Spring Petclinic API Gateway の Ingress 設定は次のようになります。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: petclinic-ingress
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
rules:
- host: api-gateway.io
http:
paths:
- path: /(.*)
pathType: Prefix
backend:
service:
name: api-gateway
port:
number: 8080
- path: /covagent/(.*)
pathType: Prefix
backend:
service:
name: api-gateway-coverage-agent
port:
number: 8051
Ingress を動作させるには、クラスタに Ingress コントローラが存在する必要があります。Minikube または MicroK8s を使用する場合、これは比較的簡単です。それぞれの Ingress アドオンを有効にするだけで、すぐに機能するはずです。kubeadm クラスタの場合は、いくつかの設定が必要です。
helm upgrade --install ingress-nginx ingress-nginx --repo https://kubernetes.github.io/ingress-nginx --namespace ingress-nginx --create-namespace --set controller.hostNetwork=true
このコマンドは、既存の ingress-nginx チャートを使用して Ingress コントローラーをセットアップします。
Ingress コントローラーが実行されていることを確認したら (ingress-nginx 名前空間内のポッドを確認してください)、Helm チャートをインストールできます。
最後に、Ingress 経由で配置されたエンドポイントにアクセスするには、エンドポイントの参照に使用するコンピューターの hosts ファイルにホスト名を追加する必要があります。Linux マシンでは /etc/hosts です。 Windows マシンでは C:\Windows\System32\drivers\etc\hosts です。
たとえば、ホスト名が api-gateway.demo である API Gatway にアクセスするには、以下の行を追加します。<IP ADDRESS>はIngress コントローラー ポッドの IP です。
<IP ADDRESS> api-gateway.demo
kubectl get ingress を使用して Ingress リソースにリストされている IP を検索することで、関連する IP を見つけることもできます。Ingress コントローラーと Kubernetes クラスタの組み合わせによっては、どちらか一方が空または空白になる場合があることに注意してください。