Crossplaneのfunction-extra-resources
は、Composition内で追加のリソースを取得するための強力なツールです。しかし、デフォルトではCrossplane関連のリソース(XRなど)しか取得できません。この記事では、この制約を回避し、任意のカスタムリソースを取得する方法を紹介します。
前提条件
- Kubernetesクラスター
- Helm
- KCL (Kubernetes Configuration Language)
- kubectl
このチュートリアルはKCLを用いますが、YAMLでも同じようなことができます。
セットアップ
1. Crossplaneのインストール
まず、Helmfileを使用してCrossplaneをインストールします。
# helmfile.yaml
repositories:
- name: crossplane-stable
url: https://charts.crossplane.io/stable
releases:
- name: crossplane
namespace: crossplane-system
chart: crossplane-stable/crossplane
version: 1.16
以下のコマンドを実行してCrossplaneをインストールします:
helmfile sync --wait
2. カスタムリソースへのアクセス権限の設定
Crossplaneがカスタムリソースにアクセスできるように、特別なClusterRoleを作成します。
# 01-cluster-roles.k
import manifests
import k8s.api.rbac.v1 as rbac
manifests.yaml_stream([
rbac.ClusterRole {
metadata.name = "suin:crossplane:additional"
rules = [
{
apiGroups = ["suin.jp"]
resources = ["clusterresources"]
verbs = ["*"]
},
{
apiGroups = ["suin.jp"]
resources = ["namespacedresources"]
verbs = ["*"]
}
]
},
rbac.ClusterRoleBinding {
metadata.name = "suin:crossplane:additional"
roleRef = {
apiGroup = "rbac.authorization.k8s.io"
kind = "ClusterRole"
name = "suin:crossplane:additional"
}
subjects = [{
kind = "ServiceAccount"
name = "crossplane"
namespace = "crossplane-system"
}]
}
])
このClusterRoleを適用します:
kcl run 01-cluster-roles.k | kubectl apply -f -
これによって、crossplane
サービスアカウントにカスタムリソースを操作する権限がつきます。これをしないと、デフォルトではfunction-extra-resources
はカスタムリソースにアクセスできません。本チュートリアルで最も重要なステップのひとつです。
ちなみに、function-extra-resources
を導入すると、この関数用のPodが作られます。このPodは、 function-extra-resources-fa66e8ab9f79
のような固有のサービスアカウントに紐づきます。そう考えると、サービスアカウントfunction-extra-resources-fa66e8ab9f79
にカスタムリソースのアクセス権限をつけるのが良さそうに思えますが、それではうまく動作しません。理由は、この関数はCrossplaneのAPIを経由してKubernetesのオブジェクトを取得しているからです。したがって、CrossplaneのAPIを提供しているPodのサービスアカウントに権限を付与する必要があります。
3. カスタムリソース定義(CRD)のインストール
テスト用のカスタムリソースを定義します。
# 02-crds.k
import manifests
import k8s.apiextensions_apiserver.pkg.apis.apiextensions.v1
manifests.yaml_stream([
v1.CustomResourceDefinition {
metadata.name = "clusterresources.suin.jp"
spec = {
group = "suin.jp"
scope = "Cluster"
names = {
kind = "ClusterResource"
plural = "clusterresources"
singular = "clusterresource"
}
versions = [{
name = "v1"
served = True
storage = True
schema.openAPIV3Schema = v1.JSONSchemaProps {
type = "object"
properties = {
spec = {type = "object"}
}
}
}]
}
},
v1.CustomResourceDefinition {
metadata.name = "namespacedresources.suin.jp"
spec = {
group = "suin.jp"
scope = "Namespaced"
names = {
kind = "NamespacedResource"
plural = "namespacedresources"
singular = "namespacedresource"
}
versions = [{
name = "v1"
served = True
storage = True
schema.openAPIV3Schema = v1.JSONSchemaProps {
type = "object"
properties = {
spec = {type = "object"}
}
}
}]
}
}
])
CRDを適用し、確立されるまで待ちます:
kcl run 02-crds.k | kubectl apply -f -
kubectl wait --for=condition=Established=true customresourcedefinition.apiextensions.k8s.io/clusterresources.suin.jp
kubectl wait --for=condition=Established=true customresourcedefinition.apiextensions.k8s.io/namespacedresources.suin.jp
ここで定義したkindのリソースを後で作り、function-extra-resources
でそのオブジェクトを取ってくるということをやる予定です。function-extra-resources
がクラスタースコープとネームスペーススコープどちらのカスタムリソースも取得可能であることを確かめるために、ここでは2つのCRDを作成しています。
4. Crossplane Functionsのインストール
必要なCrossplane Functionsをインストールします。
# 03-functions.k
import manifests
manifests.yaml_stream([
{
apiVersion = "pkg.crossplane.io/v1beta1"
kind = "Function"
metadata.name = "function-kcl"
spec.package = "xpkg.upbound.io/crossplane-contrib/function-kcl:v0.9.0"
},
{
apiVersion = "pkg.crossplane.io/v1beta1"
kind = "Function"
metadata.name = "function-auto-ready"
spec.package = "xpkg.upbound.io/crossplane-contrib/function-auto-ready:v0.2.1"
},
{
apiVersion = "pkg.crossplane.io/v1beta1"
kind = "Function"
metadata.name = "function-extra-resources"
spec.package = "xpkg.upbound.io/crossplane-contrib/function-extra-resources:v0.0.3"
}
])
Functionsをインストールし、準備が整うまで待ちます:
kcl run 03-functions.k | kubectl apply -f -
kubectl wait --for=condition=Healthy=true function.pkg.crossplane.io/function-kcl
kubectl wait --for=condition=Healthy=true function.pkg.crossplane.io/function-auto-ready
kubectl wait --for=condition=Healthy=true function.pkg.crossplane.io/function-extra-resources
5. Composite Resource Definition (XRD)の作成
テスト用のXRDを作成します。
# 04-xrds.k
import manifests
import crossplane.v1 as xp
import k8s.apiextensions_apiserver.pkg.apis.apiextensions.v1
manifests.yaml_stream([xp.CompositeResourceDefinition {
metadata = {name = "xr1.suin.jp"}
spec = {
names = {
kind = "XR1"
plural = "xr1"
}
group = "suin.jp"
versions = [{
name = "v1"
served = True
referenceable = True
schema.openAPIV3Schema = v1.JSONSchemaProps {}
}]
}
}])
XRDを適用し、確立されるまで待ちます:
kcl run 04-xrds.k | kubectl apply -f -
kubectl wait --for=condition=Established=true compositeresourcedefinitions.apiextensions.crossplane.io/xr1.suin.jp
6. Compositionの作成
function-extra-resources
を使用してカスタムリソースを取得するCompositionを作成します。
# 05-compositions.k
import crossplane.v1 as xp
import outdent
xp.Composition {
metadata = {name = "test.suin.jp"}
spec = {
compositeTypeRef = {
apiVersion = "suin.jp/v1"
kind = "XR1"
}
mode = "Pipeline"
pipeline = [
{
step = "pull-cluster-resources"
functionRef.name = "function-extra-resources"
input = {
apiVersion = "extra-resources.fn.crossplane.io/v1beta1"
kind = "Input"
spec.extraResources = [
{
apiVersion = "suin.jp/v1"
kind = "ClusterResource"
into = "cluster_resources"
type = "Selector"
selector.matchLabels = [{
type = "Value"
key = "some-label-name"
value = "some-label-value"
}]
},
{
apiVersion = "suin.jp/v1"
kind = "NamespacedResource"
into = "namespaced_resources"
type = "Selector"
selector.matchLabels = [{
type = "Value"
key = "some-label-name"
value = "some-label-value"
}]
}
]
}
},
{
step = "kcl"
functionRef.name = "function-kcl"
input = {
apiVersion = "krm.kcl.dev/v1alpha1"
kind = "KCLInput"
spec.source = outdent.outdent("""
import yaml
extra_resources = option("params")?.ctx["apiextensions.crossplane.io/extra-resources"]
print("=== found resources by function-extra-resources ===")
print("Found ${len(extra_resources.cluster_resources)} cluster resources and ${len(extra_resources.namespaced_resources)} namespaced resources.")
print(yaml.encode({
cluster_resources: [r.metadata.name for r in extra_resources.cluster_resources]
namespaced_resources: [{
name = r.metadata.name
namespace = r.metadata.namespace
} for r in extra_resources.namespaced_resources]
}))
print("===")
items = []
""")
}
}
]
}
}
このCompositionは2つのステップからなります。
-
function-extra-resources
で2種類のkindのカスタムリソースを取得する - KCLを使って上で取得したリソースをPodのログに出力する。要するにこのステップはデバッグ要員です。
Compositionを適用します:
kcl run 05-compositions.k | kubectl apply -f -
7. テスト用のリソースの作成
テスト用のNamespaceとカスタムリソースを作成します。
# 06-namespace.k
import k8s.api.core.v1
v1.Namespace {metadata.name = "my-namespace"}
# 07-crs.k
import manifests
manifests.yaml_stream([
{
apiVersion = "suin.jp/v1"
kind = "ClusterResource"
metadata.name = "cluster-resource-1"
metadata.labels = {"some-label-name" = "some-label-value"}
spec = {}
},
{
apiVersion = "suin.jp/v1"
kind = "NamespacedResource"
metadata.name = "namespaced-resource-1"
metadata.namespace = "default"
metadata.labels = {"some-label-name" = "some-label-value"}
spec = {}
},
{
apiVersion = "suin.jp/v1"
kind = "NamespacedResource"
metadata.name = "namespaced-resource-1"
metadata.namespace = "my-namespace"
metadata.labels = {"some-label-name" = "some-label-value"}
spec = {}
}
])
Namespaceとカスタムリソースを適用します:
kcl run 06-namespace.k | kubectl apply -f -
kubectl wait --for=jsonpath=status.phase=Active namespace/my-namespace
kcl run 07-crs.k | kubectl apply -f -
8. Composite Resource (XR)の作成
テスト用のXRを作成します。
# 08-xrs.k
import manifests
manifests.yaml_stream([{
apiVersion = "suin.jp/v1"
kind = "XR1"
metadata.name = "object1"
spec = {}
}])
XRを適用し、Readyになるまで待ちます:
kcl run 08-xrs.k | kubectl apply -f -
kubectl wait --for=condition=Ready=true xr1.suin.jp/object1
9. 結果の確認
function-kcl
のログを確認して、カスタムリソースが正しく取得されていることを確認します:
kubectl logs -n crossplane-system -f $(kubectl get pods -o name -n crossplane-system | grep function-kcl)
ログに次のような出力が出てくれば成功です!
=== found resources by function-extra-resources ===
Found 1 cluster resources and 2 namespaced resources.
cluster_resources:
- cluster-resource-1
namespaced_resources:
- name: namespaced-resource-1
namespace: default
- name: namespaced-resource-1
namespace: my-namespace
===
まとめ
このチュートリアルでは、Crossplaneのfunction-extra-resources
を使用して、通常はアクセスできないカスタムリソースを取得する方法を紹介しました。この技術を使用することで、Crossplaneの機能を拡張し、より柔軟なリソース管理が可能になります。
参考資料
このチュートリアルで使用したコードとリソースは、以下のGitHubリポジトリで参照できます
https://github.com/suinplayground/kubernetes-playground/blob/main/crossplane/12-function-extra-resources-to-pull-custom-resources/README.md
Crossplaneとfunction-extra-resourcesの詳細については、公式ドキュメントを参照してください。
一緒に働きませんか?
僕の会社(株式会社クラフトマンソフトウェア)では、Kubernetesエンジニアを募集しています!興味がある方はぜひ↓も御覧ください😌