![[レポート] Master KRMOps: Declarative resource management with Amazon EKS and kro #CNS407 #AWSreInvent](https://images.ctfassets.net/ct0aopd36mqt/4pUQzSdez78aERI3ud3HNg/fe4c41ee45eccea110362c7c14f1edec/reinvent2025_devio_report_w1200h630.png?w=3840&fm=webp)
[レポート] Master KRMOps: Declarative resource management with Amazon EKS and kro #CNS407 #AWSreInvent
セッションの概要
Managing cloud resources and applications in Kubernetes environments often leads to complex, fragmented deployment processes that hinder operational efficiency. Attend this advanced session to discover how to unify cloud resource management using Kubernetes Resource Model Operations (KRMOps) with kro and AWS Controllers for Kubernetes (ACK). Learn how to leverage these tools to package applications and cloud resource dependencies into cohesive deployable units using the KRMOps. You will gain practical experience in implementing declarative AWS resource management, automating dependency resolution, and enabling self-healing infrastructure - essential skills required for running large-scale Amazon EKS deployments for data and AI.
Type: Builders' session
Level: 400 – Expert
Features: Hands-on, Interactive
Topic: Developer Tools, Serverless & Containers
Area of Interest: Innovation & Transformation, Kubernetes
Role: DevOps Engineer, IT Professional / Technical Manager, Solution / Systems Architect
Services: Amazon Elastic Kubernetes Service (Amazon EKS)
Kubernetes Resource Model Operations (KRMOps) について学ぶためのハンズオンに参加してきました。
AWS Controllers for Kubernetes (ACK) を利用して Kubernetes API 経由で AWS リソースを扱えるようにしつつ、Kube Resource Orchestrator(kro) を利用してアプリケーションとクラウドリソースを上手くパッケージ化しながらデプロイする方法について実際に試してみました。
1 時間でやり切るために調整した部分はありそうでしたが、基本的には krmops-on-eks-workshop リポジトリの資材を利用していたと思います。
Terraform で AWS インフラを作成すれば同じような内容を実施できるので、興味がある方は是非試してみて下さい。
KRMOps とは
Kubernetes Resource Model (KRM) は Kubernetes の基本的なコンセプトで下記特徴があります。
- API 中心:ユーザーや各種コントローラが Kubernetes API サーバーを介してやり取りを行う。
- 宣言的制御: 構成ファイルを用いてリソースの望ましい状態を定義する。Kubernetes コントローラーは実際の状態と望ましい状態を一致させるように動作する。
- 標準化された構造と動作: metadata(ラベル、アノテーション)、spec(望ましい構成)、status(監視対象の状態)などを含むリソースの標準化された構造を定義する。
- 拡張性: KRM とそのコントローラーパターンの原則は、ACK や kro などのツールを通じてクラウドサービスなどの外部リソースの管理にも拡張できる。
KRM を Kubernetes リソースだけでなく AWS のサービスやインフラストラクチャにも拡張してより効率的に運用しようとする考え方が KRMOps です。
ACK について
ACK を利用することで Kubernetes API 経由で AWS リソースを管理でき、Kubernetes リソースと AWS リソースの依存関係管理の複雑さを軽減できます。

各 ACK サービスコントローラーは個別のコンテナイメージにパッケージ化されてパブリックリポジトリに公開されています。
プロビジョニングする AWS サービスごとに対応するコントローラーのリソースを Amazon EKS クラスターにインストールする必要があり、各 ACK サービスコントローラーは個別のコンテナイメージにパッケージ化されて Amazon ECR Public Gallery に公開されています。
今回のハンズオンでは、最初から利用するコントローラが EKS にインストールされていました。
$ environment kubectl get deployment -n ack-system
NAME READY UP-TO-DATE AVAILABLE AGE
ack-dynamodb 1/1 1 1 21h
ack-ec2 1/1 1 1 21h
ack-eks 1/1 1 1 21h
ack-iam 1/1 1 1 21h
ack-kms 1/1 1 1 21h
ack-rds 1/1 1 1 21h
ack-s3 1/1 1 1 21h
ack-secretsmanager 1/1 1 1 21h
ちなにに、先日発表された EKS Capabilities では AWS 領域にインストールされたサービスコントローラを利用可能です。
下記コマンドを実行して ACK 経由で S3 バケットを作成します。
若干複雑になっているのは S3 バケット名の重複を避けるためで、やっていることは s3.services.k8s.aws/v1alpha1 を指定してリソースを作成しているのみです。
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
export BUCKET_NAME=krmops-ack-s3-bucket-$AWS_ACCOUNT_ID
read -r -d '' BUCKET_MANIFEST <<EOF
apiVersion: s3.services.k8s.aws/v1alpha1
kind: Bucket
metadata:
name: $BUCKET_NAME
spec:
name: $BUCKET_NAME
EOF
echo "${BUCKET_MANIFEST}" > bucket.yaml
kubectl create -f bucket.yaml
ステータスを確認すると、ACK.ResourceSynced となっていることを確認できました。
$ environment kubectl describe bucket/$BUCKET_NAME
Name: krmops-ack-s3-bucket-xxxxxxxxxxxx
Namespace: default
Labels: <none>
Annotations: <none>
API Version: s3.services.k8s.aws/v1alpha1
Kind: Bucket
Metadata:
Creation Timestamp: 2025-12-03T21:11:26Z
Finalizers:
finalizers.s3.services.k8s.aws/Bucket
Generation: 1
Managed Fields:
API Version: s3.services.k8s.aws/v1alpha1
Fields Type: FieldsV1
fieldsV1:
f:metadata:
f:finalizers:
.:
v:"finalizers.s3.services.k8s.aws/Bucket":
Manager: controller
Operation: Update
Time: 2025-12-03T21:11:26Z
API Version: s3.services.k8s.aws/v1alpha1
Fields Type: FieldsV1
fieldsV1:
f:spec:
.:
f:name:
Manager: kubectl-create
Operation: Update
Time: 2025-12-03T21:11:26Z
API Version: s3.services.k8s.aws/v1alpha1
Fields Type: FieldsV1
fieldsV1:
f:status:
.:
f:ackResourceMetadata:
.:
f:arn:
f:ownerAccountID:
f:region:
f:conditions:
f:location:
Manager: controller
Operation: Update
Subresource: status
Time: 2025-12-03T21:11:27Z
Resource Version: 422722
UID: 7bda5bfe-fe61-478a-9c62-645c3505adee
Spec:
Name: krmops-ack-s3-bucket-xxxxxxxxxxxx
Status:
Ack Resource Metadata:
Arn: arn:aws:s3:::krmops-ack-s3-bucket-xxxxxxxxxxxx
Owner Account ID: xxxxxxxxxxxx
Region: us-west-2
Conditions:
Last Transition Time: 2025-12-03T21:11:27Z
Message: Resource synced successfully
Reason:
Status: True
Type: ACK.ResourceSynced
Location: http://krmops-ack-s3-bucket-xxxxxxxxxxxx.s3.amazonaws.com/
Events: <none>
無事 S3 バケットも作成されていました。
$ aws s3 ls
2025-12-03 21:11:28 krmops-ack-s3-bucket-xxxxxxxxxxxx
2025-12-02 23:40:50 krmops-s3adopt-xxxxxxxxxxxx
※ krmops-s3adopt-xxxxxxxxxxxx は今後の作業のために、元からあったリソースです。
kro について
kro で Kubernetes リソースのセットを作成する
kro をインストールすると ResourceGraphDefinition (RGD) と呼ばれるカスタムリソース定義 (CRD) を利用できるようになります。
例えば「Application Stack」という名前の RGD を作成してクラスターに適用すれば、ApplicationStack という新しい API を作成可能です。
作成したカスタム API を利用することで必要なリソースをパッケージ化してデプロイでき、ネイティブ Kubernetes リソースとクラスターにインストールされたカスタムリソース定義(CRD)の両方を含めたリソースセットにすることが可能です。

また、特定の要件に合わせてカスタマイズしながらリソースグループを利用することも可能です。

今回のハンズオンでは下記マニフェストファイルを利用して、複数の Kubernetes リソースをリソースグループにまとめて利用できるようにしました。
Deployment、Service、Ingress を一つのリソースグループにまとめています。
apiVersion: kro.run/v1alpha1
kind: ResourceGraphDefinition
metadata:
name: webapp.kro.run
spec:
schema:
apiVersion: v1alpha1
kind: WebApp
spec:
name: string
namespace: string | default=default
image: string | default=nginx
port: integer | default=8080
replicas: integer | default=1
service:
enabled: boolean | default=true
ingress:
enabled: boolean | default=false
serviceAccount: string | default=default
status:
deploymentConditions: ${deployment.status.conditions}
availableReplicas: ${deployment.status.availableReplicas}
url: ${ingress.status.loadBalancer.ingress[0].hostname}
resources:
- id: deployment
readyWhen:
- ${deployment.spec.replicas == deployment.status.availableReplicas}
template:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${schema.spec.name}
namespace: ${schema.spec.namespace}
labels:
app.kubernetes.io/name: ${schema.spec.name}
spec:
replicas: ${schema.spec.replicas}
selector:
matchLabels:
app.kubernetes.io/name: ${schema.spec.name}
app: ${schema.spec.name}
template:
metadata:
labels:
app.kubernetes.io/name: ${schema.spec.name}
app: ${schema.spec.name}
spec:
serviceAccountName: ${schema.spec.serviceAccount}
containers:
- name: webapp-demo
image: ${schema.spec.image}
imagePullPolicy: Always
ports:
- containerPort: ${schema.spec.port}
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "1"
restartPolicy: Always
- id: service
includeWhen:
- ${schema.spec.service.enabled}
template:
apiVersion: v1
kind: Service
metadata:
name: ${deployment.metadata.name}
namespace: ${deployment.metadata.namespace}
spec:
selector:
app: ${schema.spec.name}
ports:
- name: http
protocol: TCP
port: 80
targetPort: ${schema.spec.port}
- id: ingress
includeWhen:
- ${schema.spec.ingress.enabled}
template:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ${deployment.metadata.name}
namespace: ${deployment.metadata.namespace}
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/healthcheck-path: /health
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]'
alb.ingress.kubernetes.io/target-group-attributes: stickiness.enabled=true,stickiness.lb_cookie.duration_seconds=60
spec:
rules:
- http:
paths:
- path: "/"
pathType: Prefix
backend:
service:
name: ${service.metadata.name}
port:
number: 80
作成したリソースグループを利用して、インスタンスを作成します。
apiVersion: kro.run/v1alpha1
kind: WebApp
metadata:
name: test-app
spec:
name: test-app
port: 80 # nginx default port is 80
ingress:
enabled: true
service: {} # this is needed
各種リソースがまとめて作成されました。
$ webapp kubectl get deployment,service,ingress -l kro.run/instance-name=test-app
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/test-app 1/1 1 1 28s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/test-app ClusterIP 172.20.220.12 <none> 80/TCP 22s
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress.networking.k8s.io/test-app alb * k8s-default-testapp-xxxxx-xxxxxxx.us-west-2.elb.amazonaws.com 80 19s
WebApp リソースを作成だけで、アプリケーションが展開され、リクエストを正常に受け付けられるようになりました。
$ curl -s $(kubectl get ingress test-app -o jsonpath='{.status.loadBalancer.ingress[0].hostname}') | sed -n '/<body>/,/<\/body>/p' | sed -e 's/<[^>]*>//g'
Welcome to nginx!
If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.
For online documentation and support please refer to
nginx.org.
Commercial support is available at
nginx.com.
Thank you for using nginx.
kro と ACK を組み合わせて S3 バケットを作成する
まず、S3 バケットのプロビジョニング用の RGD を作成します。
kro を利用することで、バケットとバケットポリシーなどの関連するリソースをリソースグループとしてまとめることが可能です。
apiVersion: kro.run/v1alpha1
kind: ResourceGraphDefinition
metadata:
name: s3bucket.kro.run
spec:
schema:
apiVersion: v1alpha1
kind: S3Bucket
spec:
name: string
access: string | default="write"
status:
s3ARN: ${s3bucket.status.ackResourceMetadata.arn}
s3PolicyARN: ${s3PolicyWrite.status.ackResourceMetadata.arn}
resources:
- id: s3bucket
template:
apiVersion: s3.services.k8s.aws/v1alpha1
kind: Bucket
metadata:
name: ${schema.spec.name}
spec:
name: ${schema.spec.name}
- id: s3PolicyWrite
includeWhen:
- ${schema.spec.access == "write"}
template:
apiVersion: iam.services.k8s.aws/v1alpha1
kind: Policy
metadata:
name: ${schema.spec.name}-s3-write-policy
spec:
name: ${schema.spec.name}-s3-write-policy
policyDocument: |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:PutObjectAcl",
"s3:DeleteObject"
],
"Resource": [
"${s3bucket.status.ackResourceMetadata.arn}/*"
]
},
{
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetBucketLocation"
],
"Resource": [
"${s3bucket.status.ackResourceMetadata.arn}"
]
}
]
}
上記マニフェストファイルをデプロイすることで RGD として追加されます。
$ kubectl get resourcegraphdefinition
NAME APIVERSION KIND STATE AGE
s3bucket.kro.run v1alpha1 S3Bucket Active 18s
webapp.kro.run v1alpha1 WebApp Active 14m
作成された API を利用してインスタンスを作成します。
s3bucket export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
export BUCKET_NAME=krmops-kro-s3-bucket-$AWS_ACCOUNT_ID
read -r -d '' INSTANCE_MANIFEST <<EOF
apiVersion: kro.run/v1alpha1
kind: S3Bucket
metadata:
name: s3demo
namespace: default
spec:
name: $BUCKET_NAME
EOF
echo "${INSTANCE_MANIFEST}" > instance.yaml
無事インスタンスが ACTIVE になりました。
$ s3bucket kubectl get s3bucket -n default
NAME STATE SYNCED AGE
s3demo ACTIVE True 20s
S3 が作成されていることも確認できました。
$ s3bucket aws s3 ls
2025-12-03 21:11:28 krmops-ack-s3-bucket-xxxxxxxxxxxx
2025-12-03 21:32:40 krmops-kro-s3-bucket-xxxxxxxxxxxx
2025-12-02 23:40:50 krmops-s3adopt-xxxxxxxxxxxx
kro と ACK で既存リソースをインポートする
ACK では既に存在するリソースをインポートすることも可能です。
この際、下記 annotations を付与します。
| ラベル | 値 | 備考 |
|---|---|---|
| services.k8s.aws/read-only | true | ソースのステータスを同期するが、基盤となる AWS リソースの作成、更新、削除は行わない |
| services.k8s.aws/adoption-policy | adopt | インポート作業のみ、adopt-or-create を指定すればリソースがない場合に作成する |
| services.k8s.aws/adoption-field | S3 バケット名 | リソースタイプごとに固定値が決まっている |
今回は下記マニフェストファイルを利用しました。
apiVersion: kro.run/v1alpha1
kind: ResourceGraphDefinition
metadata:
name: s3bucketadopt.kro.run
spec:
schema:
apiVersion: v1alpha1
kind: S3BucketAdopt
spec:
name: string
resources:
- id: s3bucket
template:
apiVersion: s3.services.k8s.aws/v1alpha1
kind: Bucket
metadata:
name: ${schema.spec.name}
annotations:
services.k8s.aws/read-only: "true"
services.k8s.aws/adoption-policy: "adopt"
services.k8s.aws/adoption-fields: |
{
"name": "${schema.spec.name}"
}
マニフェストファイルを生成して、インポート作業を行います。
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
export BUCKET_NAME=krmops-s3adopt-$AWS_ACCOUNT_ID
read -r -d '' INSTANCE_MANIFEST <<EOF
apiVersion: kro.run/v1alpha1
kind: S3BucketAdopt
metadata:
name: s3import
namespace: default
spec:
name: $BUCKET_NAME
EOF
echo "${INSTANCE_MANIFEST}" > instance-adopt.yaml
kubectl apply -f instance-adopt.yaml
無事 InstanceSynced になり、正常にインポートすることができました。
$ kubectl describe s3bucketadopt/s3import -n default
Name: s3import
Namespace: default
Labels: kro.run/kro-version=v0.3.0
kro.run/owned=true
kro.run/resource-graph-definition-id=717f0263-5af7-4c3c-97e0-b506ee93da12
kro.run/resource-graph-definition-name=s3bucketadopt.kro.run
Annotations: <none>
API Version: kro.run/v1alpha1
Kind: S3BucketAdopt
Metadata:
Creation Timestamp: 2025-12-03T21:37:44Z
Finalizers:
kro.run/finalizer
Generation: 1
Managed Fields:
API Version: kro.run/v1alpha1
Fields Type: FieldsV1
fieldsV1:
f:metadata:
f:finalizers:
.:
v:"kro.run/finalizer":
f:labels:
.:
f:kro.run/kro-version:
f:kro.run/owned:
f:kro.run/resource-graph-definition-id:
f:kro.run/resource-graph-definition-name:
Manager: kro
Operation: Update
Time: 2025-12-03T21:37:44Z
API Version: kro.run/v1alpha1
Fields Type: FieldsV1
fieldsV1:
f:metadata:
f:annotations:
.:
f:kubectl.kubernetes.io/last-applied-configuration:
f:spec:
.:
f:name:
Manager: kubectl-client-side-apply
Operation: Update
Time: 2025-12-03T21:37:44Z
API Version: kro.run/v1alpha1
Fields Type: FieldsV1
fieldsV1:
f:status:
.:
f:conditions:
f:state:
Manager: kro
Operation: Update
Subresource: status
Time: 2025-12-03T21:37:47Z
Resource Version: 431331
UID: 6a5c4e98-164b-4df3-a304-10c6f2021970
Spec:
Name: krmops-s3adopt-783764577371
Status:
Conditions:
Last Transition Time: 2025-12-03T21:37:47Z
Message: Instance reconciled successfully
Observed Generation: 1
Reason: ReconciliationSucceeded
Status: True
Type: InstanceSynced
State: ACTIVE
Events: <none>
ネストを利用した RGB を定義して使ってみる
RGD ではフィールドをネストさせて定義することで、複数のインフラストラクチャコンポーネントを含めることが可能です。
3 つのコンポーネントをネストした、DB を扱う Web アプリケーションを作成していきます。
WebAppRds RGD
Web アプリケーションを管理する Kubernetes Deployment および、サービス公開用の Service や Ingress リソースを含んだ RGD。
DB 認証情報は Secrets Store CSI Driver を利用して扱います。
apiVersion: kro.run/v1alpha1
kind: ResourceGraphDefinition
metadata:
name: webapp.rds.kro.run
spec:
schema:
apiVersion: v1alpha1
kind: WebAppRds
spec:
name: string
namespace: string | default=default
image: string | default=nginx
port: integer | default=8080
replicas: integer | default=1
service:
enabled: boolean | default=true
serviceAccount: string | default=default
rds:
enabled: boolean | default=false
image: string
replicas: integer | default=1
dbHost: string
dbName: string | default="testdb"
status:
availableReplicas: ${rdsdeployment.status.availableReplicas}
resources:
# --- RDS Deployment (only included when rds.enabled is true) ---
- id: rdsdeployment
includeWhen:
- ${schema.spec.rds.enabled}
readyWhen:
- ${rdsdeployment.spec.replicas == rdsdeployment.status.availableReplicas}
template:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${schema.spec.name}-rdsdeployment
namespace: ${schema.spec.namespace}
labels:
app: ${schema.spec.name}-rds
spec:
replicas: ${schema.spec.rds.replicas}
selector:
matchLabels:
app: ${schema.spec.name}-rds
template:
metadata:
labels:
app: ${schema.spec.name}-rds
spec:
serviceAccountName: ${schema.spec.serviceAccount}
containers:
- name: rds-demo
image: ${schema.spec.rds.image}
imagePullPolicy: Always
ports:
- containerPort: ${schema.spec.port}
env:
- name: DB_HOST
value: ${schema.spec.rds.dbHost}
- name: DB_NAME
value: ${schema.spec.rds.dbName}
volumeMounts:
- mountPath: /mnt/secrets-store
name: dbsecret
readOnly: true
restartPolicy: Always
volumes:
- name: dbsecret
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: ${schema.spec.name}-aws-secrets
# --- Service for RDS App ---
- id: rdsservice
includeWhen:
- ${schema.spec.service.enabled}
- ${schema.spec.rds.enabled}
readyWhen:
- ${has(rdsservice.spec.clusterIP)}
template:
apiVersion: v1
kind: Service
metadata:
name: ${schema.spec.name}-rdsservice
namespace: ${schema.spec.namespace}
labels:
app: ${schema.spec.name}-rds
spec:
selector:
app: ${schema.spec.name}-rds
ports:
- port: 80
targetPort: ${schema.spec.port}
protocol: TCP
type: ClusterIP
# --- Ingress for ALB ---
- id: rdsingress
includeWhen:
- ${schema.spec.service.enabled}
- ${schema.spec.rds.enabled}
readyWhen:
- ${has(rdsingress.status.loadBalancer)}
template:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ${schema.spec.name}-rdsingress
namespace: ${schema.spec.namespace}
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP":80}]'
labels:
app: ${schema.spec.name}-rds
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ${schema.spec.name}-rdsservice
port:
number: 80
RdsInstance RGD
RDS インスタンス関連リソースを含む RGD。
apiVersion: kro.run/v1alpha1
kind: ResourceGraphDefinition
metadata:
name: rds.kro.run
spec:
schema:
apiVersion: v1alpha1
kind: RdsInstance
spec:
awsAccountID: integer
awsRegion: string
dbEngine: string
environment: string
namespace: string
applicationName: string
vpcID: string
cidrIP: string
subnetIDs: "[]string"
status:
endpointAddress: ${awsDatabase.status.endpoint.address}
secretARN: ${awsDatabase.status.masterUserSecret.secretARN}
secretPolicyARN: ${secretManagerPolicy.status.ackResourceMetadata.arn}
resources:
- id: subnetGroup
template:
apiVersion: rds.services.k8s.aws/v1alpha1
kind: DBSubnetGroup
metadata:
name: db-subnet-group-${schema.spec.applicationName}
spec:
description: db test
name: db-sb-${schema.spec.applicationName}
subnetIDs: ${schema.spec.subnetIDs}
- id: securityGroup
template:
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: SecurityGroup
metadata:
name: db-security-group-${schema.spec.applicationName}
spec:
vpcID: ${schema.spec.vpcID}
name: db-sg-${schema.spec.applicationName}
description: inress mysql rule
ingressRules:
- fromPort: 3306
toPort: 3306
ipProtocol: TCP
ipRanges:
- cidrIP: ${schema.spec.cidrIP}
description: db test
- id: awsDatabase
readyWhen:
- ${awsDatabase.status.conditions.exists(x, x.type == 'ACK.ResourceSynced' && x.status == "True")}
template:
apiVersion: rds.services.k8s.aws/v1alpha1
kind: DBInstance
metadata:
name: ${schema.spec.applicationName}-db-${schema.spec.environment}
spec:
manageMasterUserPassword: true
masterUsername: testUser
allocatedStorage: 10
dbInstanceClass: db.t4g.micro
dbInstanceIdentifier: ${schema.spec.applicationName}-db-${schema.spec.environment}
engine: ${schema.spec.dbEngine}
engineVersion: "8.0"
dbSubnetGroupName: db-sb-${schema.spec.applicationName}
vpcSecurityGroupIDs:
- ${securityGroup.status.id}
- id: secretManagerPolicy
template:
apiVersion: iam.services.k8s.aws/v1alpha1
kind: Policy
metadata:
name: ${schema.spec.applicationName}-secretsmanager-policy
spec:
name: ${schema.spec.applicationName}-secretsmanager-policy
policyDocument: |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": [
"${awsDatabase.status.masterUserSecret.secretARN}"
]
},
{
"Effect": "Allow",
"Action": [
"secretsmanager:ListSecrets"
],
"Resource": ["*"]
}
]
}
- id: secretProviderClass
template:
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: ${schema.spec.applicationName}-aws-secrets
spec:
provider: aws
parameters:
objects: |
- objectName: "${awsDatabase.status.masterUserSecret.secretARN}"
objectType: "secretsmanager"
objectAlias: "dbsecret"
usePodIdentity: "true"
PodIdentity RGD
Pod Identity で利用できる IAM ロールを含む RGD。
apiVersion: kro.run/v1alpha1
kind: ResourceGraphDefinition
metadata:
name: podidentity.kro.run
spec:
schema:
apiVersion: v1alpha1
kind: PodIdentity
spec:
name: string
clusterName: string | default="kro"
policyARN: string | default=""
status:
serviceAccount: ${serviceaccount.metadata.name}
resources:
- id: role
template:
apiVersion: iam.services.k8s.aws/v1alpha1
kind: Role
metadata:
name: ${schema.spec.name}-role
spec:
name: ${schema.spec.name}-role
policies:
- ${schema.spec.policyARN}
assumeRolePolicyDocument: |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "pods.eks.amazonaws.com"
},
"Action": [
"sts:AssumeRole",
"sts:TagSession"
]
}
]
}
DbWebStack RGD
下記 3 つのコンポーネントをネストした RGD。
- WebAppRds RGD
- RdsInstance RGD
- PodIdentity RGD
apiVersion: kro.run/v1alpha1
kind: ResourceGraphDefinition
metadata:
name: db-webstack.kro.run
spec:
schema:
apiVersion: v1alpha1
kind: DbWebStack
spec:
name: string
applicationName: string
clusterName: string | default="kro"
image: string
port: integer | default=8080
replicas: integer | default=1
service:
enabled: boolean | default=true
rds:
enabled: boolean | default=false
dbEngine: string | default="mysql"
environment: string | default="dev"
awsAccountID: integer
awsRegion: string
vpcID: string
cidrIP: string
subnetIDs: "[]string"
status:
deploymentConditions: ${webapp.status.conditions}
availableReplicas: ${webapp.status.availableReplicas}
resources:
- id: awsDatabase
includeWhen:
- ${schema.spec.rds.enabled}
readyWhen:
- "${has(awsDatabase.status.endpointAddress) && has(awsDatabase.status.secretPolicyARN)}"
template:
apiVersion: kro.run/v1alpha1
kind: RdsInstance
metadata:
name: ${schema.spec.name}
spec:
awsRegion: ${schema.spec.rds.awsRegion}
dbEngine: ${schema.spec.rds.dbEngine}
environment: ${schema.spec.rds.environment}
namespace: default
applicationName: ${schema.spec.name}
vpcID: ${schema.spec.rds.vpcID}
cidrIP: ${schema.spec.rds.cidrIP}
subnetIDs: ${schema.spec.rds.subnetIDs}
- id: podidentity
readyWhen:
- "${has(podidentity.status.serviceAccount) && size(podidentity.status.serviceAccount) > 0}"
template:
apiVersion: kro.run/v1alpha1
kind: PodIdentity
metadata:
name: ${schema.spec.name}
spec:
name: ${schema.spec.name}
policyARN: ${awsDatabase.status.secretPolicyARN}
- id: webapp
readyWhen:
- "${has(webapp.status.availableReplicas) && webapp.status.availableReplicas > 0}"
template:
apiVersion: kro.run/v1alpha1
kind: WebAppRds
metadata:
name: ${schema.spec.name}
spec:
name: ${schema.spec.name}
image: ${schema.spec.image}
port: ${schema.spec.port}
replicas: ${schema.spec.replicas}
serviceAccount: ${podidentity.status.serviceAccount}
service:
enabled: ${schema.spec.service.enabled}
rds:
enabled: ${schema.spec.rds.enabled}
image: ${schema.spec.image}
replicas: 1
dbHost: ${awsDatabase.status.endpointAddress}
dbName: "testdb"
このように定義することで、依存関係は上手く kro が扱ってくれます。
アプリケーションと合わせて RDS インスタンスや Secret Manager、Pod Identity による権限付与などを一つのパッケージとして扱うことができています。
インスタンス作成
作成されたカスタム API を利用してインスタンスを作成します。
apiVersion: kro.run/v1alpha1
kind: DbWebStack
metadata:
name: webapp-instance
spec:
applicationName: webapp-rds
name: webapp-rds
image: xxxxxxxx.dkr.ecr.us-west-2.amazonaws.com/krmops-ecr-repo:rds-latest
port: 8080
replicas: 2
service:
enabled: true
rds:
enabled: true
awsRegion: us-west-2
vpcID: vpc-040d4d84ffa71830f
cidrIP: 10.0.0.0/16
subnetIDs:
- subnet-024f6b3530ded492c
- subnet-03cfe9ea01f066dbe
RDS 作成が含まれるので少し時間がかかりますが、9 分程度で STATE が ACTIVE になりました。
$ kubectl get rdsinstance
NAME STATE SYNCED AGE
webapp-rds ACTIVE True 8m49s
アプリケーションログを見る限り正常に動作していそうです。
Ingress 経由で払い出された URL にアクセスすると、アプリケーションが作成されて DB 接続も正常に動作していることを確認できました。

最後に
kro は難しそうで触れていなかったため、一通り試せてとても満足度が高かったです。
取っつき辛さもあまり無く、かなりシンプルに利用できる印象です。
EKS Capabilities でインストール作業や管理も簡単になったので、複数の Kubernetes リソースをまとめてパッケージ化したい場合は今後選択肢に入ってくるのかなと思います。
まだまだ成長中のツールだと思っているので、今後の機能強化等もキャッチアップしていきたいなと思いました。







