はじめに
これはKMC Advent Calendar 2023の20日目の記事です。
昨日はpollenJPさんの【Rust製構成管理ツール】JetPorch とは【次世代Ansible?】 でした。Rust製は気になりますね。マネージドノードへの要件がないことに魅力を感じました。(yamlなのはそうなんだ......と思いもしましたが)
これはkubeadm+flannelでkubernetesクラスタのIPv6環境構築の続きです。
前回KubernetesクラスタでIPv6環境を構築しました。しかし、問題点として作成するすべてのサービスが元は
spec:
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
になっているため、
spec:
ipFamilies:
- IPv4
- IPv6
ipFamilyPolicy: PreferDualStack
を付け加える必要がありました。
このような作業を毎回やるのは現実的ではありません。そのため、今回はKubebuilderを使用して、サービスを作成するとspec.ipFamilies
とspec.ipFamilyPolicy
を動的に書き換えてくれるMutatingAdmissionWebhookを作成しました。
作成したKubebuilderはsegre5458/k8s-ipv6-webhookにあります。
MutatingAdmissionWebhookとは
MutatingAdmissionWebhookはAdmission Controllerのプラグインの一つです。
詳しくはA Guide to Kubernetes Admission ControllersやAdmission Webhookを作って遊んで、その仕組みを理解しよう(説明編)などを参照してください。
ここではAdmission Webhookのみの解説を行います。
KubernetesのAdmission WebhookにはMutatingAdmissionWebhookとValidatingAdmissionWebhookがあります。
MutatingAdmissionWebhookはリソース作成時にリソースに対する変更を実装します。例としてPodの名前の変更やAnnotationの書き換えなどを行うことができます。KubebuilderではMutatingAdmissionWebhookはDefaultingAdmissionWebhookという名称になっています。
一方、ValidatingAdmissionWebhookはリソース作成/変更/削除時にリソースの検証を行うことができます。
以下はA Guide to Kubernetes Admission Controllersで提示されている図です。この図から分かる通りMutatingAdmissionWebhookはシリアルに実行され、ValidatingAdmissionWebhooknはその後にパラレルに実行されます。
Kubebuilderとは
Kubebuilderは、カスタムリソース定義(CRD)を使用してKubernetes APIを構築するためのフレームワークです。また、CRDの作成以外にもすでに存在するリソースへのWebhookを作成することもできます。今回はServiceの作成時に呼び出されるWebhookを作成するため、これを使用します。
Kubebuilderを理解するにはつくって学ぶKubebuilderを一通り行うことをおすすめします。
今回はこの本に従ってKubebuilderを作成していきます。
なお、MutatingAdmissionWebhookのみを作成する場合はKubebuilderを利用しなくても作成することもできますが、Kubebuilderは通常必要な以下の実装を行ってくれます。
- Webhook サーバの作成
- サーバがマネージャーに追加されていることの確認
- webhook用のハンドラの作成
- 各ハンドラのサーバーへの追加
MutatingAdmissionWebhookのみを作成した同様の実装はすでに存在するのでそちらを利用してもいいでしょう。
https://github.com/tibordp/prefer-dual-stack-webhook
Webhookの作成
それではKubebuilderを利用してWebhookを作成していきましょう。
Kubebuilerのインストール
Quick Startにしたがって
- kubebuilder
- go
- docker
- kubectl
のインストールを行ってください
雛形の生成
まずはプロジェクトの雛形を作成します。
mkdir k8s-ipv6-webhook
cd k8s-ipv6-webhook
kubebuilder init --domain webhook.marokiki.net --repo github.com/segre5458/k8s-ipv6-webhook
-
--domain
: domainで指定した値がCRDのグループ名となります。そのため、すべてのAPIグループ名が<group>.webhook.marokiki.net
となります。唯一の値を指定してください -
--repo
: repoにはgo modulesのmodule名を指定します。GitHubを用いる場合はGitHubのリポジトリを指定すると良いでしょう
続いて、apiの雛形を生成します。
kubebuilder create api --group core --kind Service --resource=false --controller=false --version v1
-
--group
: 今回はService.Spec.IPFamilyPolicy
を書き換えることを目的としているため、利用するパッケージはk8s.io/api/core/v1となります。よってgroupにはcoreを指定します -
--kind
: 先程と同様の理由でkindにはserviceを指定します。独自のカスタムコントローラを作成する場合は独自の値を指定します -
--resouce
,--controller
: カスタムリソースとカスタムコントローラを作成する場合はtrue
を指定します。今回は作成しないためfalseとしておきます -
--version
: API Versionを指定します。 先程指定したdomain
とgroup
に基づき、APIは<group>.<domain>/<version>
となりますが、今回は既存のグループを指定しているため、k8s.io/api/core/v1
に作成されます
続いてwebhookの雛形を生成します。
kubebuilder create webhook --group core --version v1 --kind Service --defaulting --programmatic-validation
--defaulting
と--programmatic-validation
を指定することでDefaultingWebhookとValidationWebhookの雛形が生成されます。
以上で雛形の生成は以上になります。現在のディレクトリ階層は
.
|-- Dockerfile
|-- Makefile
|-- PROJECT
|-- README.md
|-- api
| `-- v1
| |-- service_webhook.go
| `-- webhook_suite_test.go
|-- bin
| `-- controller-gen
|-- cmd
| `-- main.go
|-- config
| |-- certmanager
| | |-- certificate.yaml
| | |-- kustomization.yaml
| | `-- kustomizeconfig.yaml
| |-- crd
| | `-- patches
| | |-- cainjection_in_services.yaml
| | `-- webhook_in_services.yaml
| |-- default
| | |-- kustomization.yaml
| | |-- manager_auth_proxy_patch.yaml
| | |-- manager_config_patch.yaml
| | |-- manager_webhook_patch.yaml
| | `-- webhookcainjection_patch.yaml
| |-- manager
| | |-- kustomization.yaml
| | `-- manager.yaml
| |-- prometheus
| | |-- kustomization.yaml
| | `-- monitor.yaml
| |-- rbac
| | |-- auth_proxy_client_clusterrole.yaml
| | |-- auth_proxy_role.yaml
| | |-- auth_proxy_role_binding.yaml
| | |-- auth_proxy_service.yaml
| | |-- kustomization.yaml
| | |-- leader_election_role.yaml
| | |-- leader_election_role_binding.yaml
| | |-- role.yaml
| | |-- role_binding.yaml
| | `-- service_account.yaml
| `-- webhook
| |-- kustomization.yaml
| |-- kustomizeconfig.yaml
| `-- service.yaml
|-- go.mod
|-- go.sum
`-- hack
`-- boilerplate.go.txt
となっています。以上の雛形はKubeBuilderVersion 3.13.0 で生成したものになります。バージョンによって雛形は異なる可能性が高いです。
プロジェクト作成
では雛形からWebhookを作成していきます。
まず、internal/
は生成されていないため、Dockerfile
内のCOPY internal/controller/ internal/controller/
の記述を削除します。
# Build the manager binary
FROM golang:1.20 as builder
ARG TARGETOS
ARG TARGETARCH
WORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum
# cache deps before building and copying source so that we don't need to re-download as much
# and so that source changes don't invalidate our downloaded layer
RUN go mod download
# Copy the go source
COPY cmd/main.go cmd/main.go
COPY api/ api/
- COPY internal/controller/ internal/controller/
+ # COPY internal/controller/ internal/controller/
# Build
# the GOARCH has not a default value to allow the binary be built according to the host where the command
# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go
# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /workspace/manager .
USER 65532:65532
ENTRYPOINT ["/manager"]
続いてconfig/default/kustomization.yaml
を編集します。
ここでは使用するリソースを指定します。
-
#- ../certmanager
のコメントアウトを消す -
#- path: webhookcainjection_patch.yaml
のコメントアウトを消す -
replace
以下のコメントアウトを全て消す
# Adds namespace to all resources.
namespace: k8s-ipv6-webhook-system
# Value of this field is prepended to the
# names of all resources, e.g. a deployment named
# "wordpress" becomes "alices-wordpress".
# Note that it should also match with the prefix (text before '-') of the namespace
# field above.
namePrefix: k8s-ipv6-webhook-
# Labels to add to all resources and selectors.
#labels:
#- includeSelectors: true
# pairs:
# someName: someValue
resources:
#- ../crd
- ../rbac
- ../manager
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
# crd/kustomization.yaml
- ../webhook
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.
- #- ../certmanager
+ - ../certmanager
# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.
#- ../prometheus
patches:
# Protect the /metrics endpoint by putting it behind auth.
# If you want your controller-manager to expose the /metrics
# endpoint w/o any authn/z, please comment the following line.
- path: manager_auth_proxy_patch.yaml
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
# crd/kustomization.yaml
- path: manager_webhook_patch.yaml
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'.
# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks.
# 'CERTMANAGER' needs to be enabled to use ca injection
- #- path: webhookcainjection_patch.yaml
+ - path: webhookcainjection_patch.yaml
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.
# Uncomment the following replacements to add the cert-manager CA injection annotations
- #replacements:
- # - source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs
- # kind: Certificate
- # group: cert-manager.io
- # version: v1
- # name: serving-cert # this name should match the one in certificate.yaml
- # fieldPath: .metadata.namespace # namespace of the certificate CR
- # targets:
- # - select:
- # kind: ValidatingWebhookConfiguration
- # fieldPaths:
- # - .metadata.annotations.[cert-manager.io/inject-ca-from]
- # options:
- # delimiter: '/'
- # index: 0
- # create: true
- # - select:
- # kind: MutatingWebhookConfiguration
- # fieldPaths:
- # - .metadata.annotations.[cert-manager.io/inject-ca-from]
- # options:
- # delimiter: '/'
- # index: 0
- # create: true
- # - select:
- # kind: CustomResourceDefinition
- # fieldPaths:
- # - .metadata.annotations.[cert-manager.io/inject-ca-from]
- # options:
- # delimiter: '/'
- # index: 0
- # create: true
- # - source:
- # kind: Certificate
- # group: cert-manager.io
- # version: v1
- # name: serving-cert # this name should match the one in certificate.yaml
- # fieldPath: .metadata.name
- # targets:
- # - select:
- # kind: ValidatingWebhookConfiguration
- # fieldPaths:
- # - .metadata.annotations.[cert-manager.io/inject-ca-from]
- # options:
- # delimiter: '/'
- # index: 1
- # create: true
- # - select:
- # kind: MutatingWebhookConfiguration
- # fieldPaths:
- # - .metadata.annotations.[cert-manager.io/inject-ca-from]
- # options:
- # delimiter: '/'
- # index: 1
- # create: true
- # - select:
- # kind: CustomResourceDefinition
- # fieldPaths:
- # - .metadata.annotations.[cert-manager.io/inject-ca-from]
- # options:
- # delimiter: '/'
- # index: 1
- # create: true
- # - source: # Add cert-manager annotation to the webhook Service
- # kind: Service
- # version: v1
- # name: webhook-service
- # fieldPath: .metadata.name # namespace of the service
- # targets:
- # - select:
- # kind: Certificate
- # group: cert-manager.io
- # version: v1
- # fieldPaths:
- # - .spec.dnsNames.0
- # - .spec.dnsNames.1
- # options:
- # delimiter: '.'
- # index: 0
- # create: true
- # - source:
- # kind: Service
- # version: v1
- # name: webhook-service
- # fieldPath: .metadata.namespace # namespace of the service
- # targets:
- # - select:
- # kind: Certificate
- # group: cert-manager.io
- # version: v1
- # fieldPaths:
- # - .spec.dnsNames.0
- # - .spec.dnsNames.1
- # options:
- # delimiter: '.'
- # index: 1
- # create: true
+ replacements:
+ - source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs
+ kind: Certificate
+ group: cert-manager.io
+ version: v1
+ name: serving-cert # this name should match the one in certificate.yaml
+ fieldPath: .metadata.namespace # namespace of the certificate CR
+ targets:
+ - select:
+ kind: ValidatingWebhookConfiguration
+ fieldPaths:
+ - .metadata.annotations.[cert-manager.io/inject-ca-from]
+ options:
+ delimiter: '/'
+ index: 0
+ create: true
+ - select:
+ kind: MutatingWebhookConfiguration
+ fieldPaths:
+ - .metadata.annotations.[cert-manager.io/inject-ca-from]
+ options:
+ delimiter: '/'
+ index: 0
+ create: true
+ - select:
+ kind: CustomResourceDefinition
+ fieldPaths:
+ - .metadata.annotations.[cert-manager.io/inject-ca-from]
+ options:
+ delimiter: '/'
+ index: 0
+ create: true
+ - source:
+ kind: Certificate
+ group: cert-manager.io
+ version: v1
+ name: serving-cert # this name should match the one in certificate.yaml
+ fieldPath: .metadata.name
+ targets:
+ - select:
+ kind: ValidatingWebhookConfiguration
+ fieldPaths:
+ - .metadata.annotations.[cert-manager.io/inject-ca-from]
+ options:
+ delimiter: '/'
+ index: 1
+ create: true
+ - select:
+ kind: MutatingWebhookConfiguration
+ fieldPaths:
+ - .metadata.annotations.[cert-manager.io/inject-ca-from]
+ options:
+ delimiter: '/'
+ index: 1
+ create: true
+ - select:
+ kind: CustomResourceDefinition
+ fieldPaths:
+ - .metadata.annotations.[cert-manager.io/inject-ca-from]
+ options:
+ delimiter: '/'
+ index: 1
+ create: true
+ - source: # Add cert-manager annotation to the webhook Service
+ kind: Service
+ version: v1
+ name: webhook-service
+ fieldPath: .metadata.name # namespace of the service
+ targets:
+ - select:
+ kind: Certificate
+ group: cert-manager.io
+ version: v1
+ fieldPaths:
+ - .spec.dnsNames.0
+ - .spec.dnsNames.1
+ options:
+ delimiter: '.'
+ index: 0
+ create: true
+ - source:
+ kind: Service
+ version: v1
+ name: webhook-service
+ fieldPath: .metadata.namespace # namespace of the service
+ targets:
+ - select:
+ kind: Certificate
+ group: cert-manager.io
+ version: v1
+ fieldPaths:
+ - .spec.dnsNames.0
+ - .spec.dnsNames.1
+ options:
+ delimiter: '.'
+ index: 1
+ create: true
次に、webhook/manifests.yaml
を作成します。
初期状態では、webhook/kustomization.yaml
にmanifests.yaml
が含まれているにも関わらず生成されていません。
resources:
- manifests.yaml
- service.yaml
configurations:
- kustomizeconfig.yaml
webhook/manifests.yaml
の内容は以下のとおりです。
---
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: mutating-webhook-configuration
webhooks:
- admissionReviewVersions:
- v1
clientConfig:
service:
name: webhook-service
namespace: system
path: /mutate--v1-service
failurePolicy: Fail
name: mutate.service.webhook.marokiki.net
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- services
sideEffects: None
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: validating-webhook-configuration
webhooks:
- admissionReviewVersions:
- v1
clientConfig:
service:
name: webhook-service
namespace: system
path: /validate--v1-service
failurePolicy: Fail
name: validate.service.webhook.marokiki.net
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- services
sideEffects: None
また、manager/kustomization.yaml
を以下のように変更し、Doceker imageの設定を行います。
resources:
- manager.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
images:
- name: controller
newName: segre5458/k8s-ipv6-webhook
newTag: latest
以上でWebhookを動かす設定ができます。
続いてcmd/main.go
を編集してWebhookの実装を作成していきます。
上述した通り、k8s.io/api/core/v1
を利用するのでimport
に
corev1 "k8s.io/api/core/v1"
v1 "github.com/segre5458/k8s-ipv6-webhook/api/v1"
を追加します。
また、SetupWebhookWithManager
はカスタムコントローラ用の関数であるため変更します。
/*
Copyright 2023.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"flag"
"os"
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
+ corev1 "k8s.io/api/core/v1"
+ v1 "github.com/segre5458/k8s-ipv6-webhook/api/v1"
//+kubebuilder:scaffold:imports
)
var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
)
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
//+kubebuilder:scaffold:scheme
}
func main() {
var metricsAddr string
var enableLeaderElection bool
var probeAddr string
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
opts := zap.Options{
Development: true,
}
opts.BindFlags(flag.CommandLine)
flag.Parse()
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
Metrics: metricsserver.Options{BindAddress: metricsAddr},
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "9c406f55.webhook.marokiki.net",
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
// when the Manager ends. This requires the binary to immediately end when the
// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
// speeds up voluntary leader transitions as the new leader don't have to wait
// LeaseDuration time first.
//
// In the default scaffold provided, the program ends immediately after
// the manager stops, so would be fine to enable this option. However,
// if you are doing or is intended to do any operation such as perform cleanups
// after the manager stops then its usage might be unsafe.
// LeaderElectionReleaseOnCancel: true,
})
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}
- if os.Getenv("ENABLE_WEBHOOKS") != "false" {
- if err = (&corev1.Service{}).SetupWebhookWithManager(mgr); err != nil {
- setupLog.Error(err, "unable to create webhook", "webhook", "Service")
- os.Exit(1)
- }
- }
+ ctrl.NewWebhookManagedBy(mgr).
+ WithDefaulter(&v1.ServiceNetwork{}).
+ WithValidator(&v1.ServiceNetwork{}).
+ For(&corev1.Service{}).
+ Complete()
//+kubebuilder:scaffold:builder
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up health check")
os.Exit(1)
}
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up ready check")
os.Exit(1)
}
setupLog.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
}
最後に、api/v1/service_webhook.go
を編集してWebhookを作成します。
-
SetupWebhookWithManager
はcmd/main.go
に記述したため削除します -
ServiceNetwork
という新しい構造体を定義し、admission.CustomDefaulter
とadmission.CustomValidater
にこれを指定し、この構造体にメソッドを実装していきます -
//+kubebuilder
はkubebuilderマーカーであり、webhook マニフェストを生成するため使用されています。このマーカーはwebhook マニフェストを生成します。そのため、一部変更しています。詳しくはdocumentを参照して下さい
/*
Copyright 2023.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
import (
+ "context"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+ corev1 "k8s.io/api/core/v1"
)
// log is for logging in this package.
var servicelog = logf.Log.WithName("service-resource")
- // SetupWebhookWithManager will setup the manager to manage the webhooks
- func (r *Service) SetupWebhookWithManager(mgr ctrl.Manager) error {
- return ctrl.NewWebhookManagedBy(mgr).
- For(r).
- Complete()
- }
// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
- //+kubebuilder:webhook:path=/mutate-core-v1-service,mutating=true,failurePolicy=fail,sideEffects=None,groups=core,resources=services,verbs=create;update,versions=v1,name=mservice.kb.io,admissionReviewVersions=v1
+ //+kubebuilder:webhook:path=/mutate--v1-service,mutating=true,failurePolicy=fail,sideEffects=None,groups=core,resources=services,verbs=create;update,versions=v1,name=mutate.service.webhook.marokiki.net,admissionReviewVersions=v1
+ type ServiceNetwork struct{}
- var _ webhook.Defaulter = &Service{}
+ var _ admission.CustomDefaulter = &ServiceNetwork{}
// Default implements webhook.Defaulter so a webhook will be registered for the type
- func (r *Service) Default() {
- servicelog.Info("default", "name", r.Name)
-
- // TODO(user): fill in your defaulting logic.
- }
+ func (*ServiceNetwork) Default(ctx context.Context, obj runtime.Object) error {
+ service := obj.(*corev1.Service)
+ servicelog.Info("default", "name", service.Name)
+
+ if service.Spec.IPFamilyPolicy == nil {
+ service.Spec.IPFamilyPolicy = new(corev1.IPFamilyPolicy)
+ }
+ *service.Spec.IPFamilyPolicy = corev1.IPFamilyPolicyPreferDualStack
+ *&service.Spec.IPFamilies = []corev1.IPFamily{corev1.IPv4Protocol, corev1.IPv6Protocol}
+
+ return nil
+ }
// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
- //+kubebuilder:webhook:path=/validate-core-v1-service,mutating=false,failurePolicy=fail,sideEffects=None,groups=core,resources=services,verbs=create;update,versions=v1,name=vservice.kb.io,admissionReviewVersions=v1
+ //+kubebuilder:webhook:path=/validate--v1-service,mutating=false,failurePolicy=fail,sideEffects=None,groups=core,resources=services,verbs=create;update,versions=v1,name=validate.service.webhook.marokiki.net,admissionReviewVersions=v1
- var _ webhook.Validator = &Service{}
+ var _ admission.CustomValidator = &ServiceNetwork{}
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
- func (r *Service) ValidateCreate() (admission.Warnings, error) {
- servicelog.Info("validate create", "name", r.Name)
-
- // TODO(user): fill in your validation logic upon object creation.
- return nil, nil
- }
+ func (*ServiceNetwork) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
+ service := obj.(*corev1.Service)
+ servicelog.Info("validate create", "name", service.Name)
+
+ return nil, nil
+ }
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
- func (r *Service) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
- servicelog.Info("validate update", "name", r.Name)
-
- // TODO(user): fill in your validation logic upon object update.
- return nil, nil
- }
+ func (*ServiceNetwork) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
+ service := newObj.(*corev1.Service)
+ servicelog.Info("validate update", "name", service.Name)
+
+ return nil, nil
+ }
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
- func (r *Service) ValidateDelete() (admission.Warnings, error) {
- servicelog.Info("validate delete", "name", r.Name)
-
- // TODO(user): fill in your validation logic upon object deletion.
- return nil, nil
- }
+ func (*ServiceNetwork) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
+ return nil, nil
+ }
DefalutingWebhookの実装はfunc (*ServiceNetwork) Default(ctx context.Context, obj runtime.Object) error
に記述します。今回はIPFamilyPolicy
をPreferDualStack
し、IPFamilies
にIPv4
とIPv6
の両方を指定しています。なお、IPFamilies
の指定の順番には意味があります。過去の記事を参照してください。
service.Spec.IPFamilyPolicy
がnilの場合はnew
しています。記述はImplementing defaulting/validating webhooksを参考にしています。
func (*ServiceNetwork) Default(ctx context.Context, obj runtime.Object) error {
service := obj.(*corev1.Service)
servicelog.Info("default", "name", service.Name)
if service.Spec.IPFamilyPolicy == nil {
service.Spec.IPFamilyPolicy = new(corev1.IPFamilyPolicy)
}
*service.Spec.IPFamilyPolicy = corev1.IPFamilyPolicyPreferDualStack
*&service.Spec.IPFamilies = []corev1.IPFamily{corev1.IPv4Protocol, corev1.IPv6Protocol}
return nil
}
ValidationWebhookの記述はValidateCreate
、ValidateUpdate
、ValidateDelete
に記述します。それぞれ、作成、更新、削除時に実行されます。今回は使用しないため重要な変更は行っていません。
以上でWebhookの作成は終了です。
Docker Build
それでは作成したWebhookをBuildしてみましょう。
sudo make docker-build IMG=segre5458/k8s-ipv6-webhook:latest
ghcr.ioに作成したimageをpushします。
sudo docker login ghcr.io -u segre5458
sudo docker tag f44f9df79682 ghcr.io/segre5458/k8s-ipv6-webhook/webhook:latest
sudo docker push ghcr.io/segre5458/k8s-ipv6-webhook/webhook:latest
Kubernetesで使用する
Configをapplyすることで作成したWebhookを使用できます。
kustomize build config/default | kubectl apply -f -
適当なServiceを作成するとIPFamilyPolicy
とIPFamilies
が書き換わっているのが確認できると思います。
おわりに
以上で快適なk8s IPv6環境を作成することができました!
Kubebuilderを使うとHTTPSサーバを自分で作らなくてもいいといった利点がある一方で、基本的にカスタムリソースを作成するための雛形が生成されるため、Webhookだけを作成したいとなると変更しなければならない箇所が多くやや大変でした。
Kubebuilderにも入門できたので今度はCRDの作成もしてみたいです。
明日のAdvent Calenderの記事はACamusさんです。