Использование CRD в качестве API

cdk8s (Cloud Development Kit for Kubernetes) — это фреймворк с открытым исходным кодом (часть CNCF), с помощью которого вы можете определять свои приложения Kubernetes с помощью обычных языков программирования (вместо yaml). В некоторых из предыдущих блогов на эту тему рассказывалось о том, как начать работу и использовать библиотеку cdk8s-plus для дальнейшего улучшения основных функций библиотеки cdk8s. Мы продолжим и продвинем cdk8s еще дальше.

Этот пост в блоге продемонстрирует, как вы можете использовать Определения пользовательских ресурсов Kubernetes с cdk8s. Мы начнем с простого примера Nginx, а затем вы будете использовать комбинацию CRD проекта Strimzi вместе с Go и cdk8s для определения и развертывания кластера Kafka в Kubernetes!

Я предполагаю, что вы немного знакомы с определениями пользовательских ресурсов Kubernetes и, возможно, даже использовали некоторые из них в форме операторов. Если нет, то все в порядке! Документация Kubernetes достаточно хорошо описывает это. Вы всегда можете обратиться к нему, вернуться сюда и продолжить!

cdk8s позволяет использовать объекты API Kubernetes непосредственно в коде без необходимости импортировать отдельные клиентские пакеты Go, и все это благодаря cdk8s import. (также упоминается в разделе 'Подождите, а как насчет зависимостей Kubernetes API??' в предыдущем сообщении в блоге). Но вы также можете использовать его для пользовательских определений ресурсов! Давайте посмотрим на это в действии.

Прежде чем вы начнете…

Убедитесь, что у вас установлены Go (v1.16 или выше) и cdk8s CLI. Кроме того, вам необходимо иметь доступ к кластеру Kubernetes. Для обучения и экспериментов я бы рекомендовал использовать одноузловой кластер, работающий локально — типа minikube, kind и т. д.

Обычно я использую minikube, поэтому настроить кластер так же просто, как minikube start

Чтобы установить cdk8s CLI:

Вы можете выбрать один из следующих вариантов:

#homebrew
brew install cdk8s
#npm
npm install -g cdk8s-cli
#yarn
yarn global add cdk8s-cli

Хорошо, приступим…

Хотя в этом сообщении блога содержатся пошаговые инструкции, вы всегда можете обратиться к полному коду на Github

cdk8s упрощает начало работы и загрузку вашего приложения. Вам не нужно гадать и выяснять, как структурировать ваш проект, устанавливать зависимости и т. д., поскольку команда cdk8s init сделает это за вас!

cdk8s init go-app
#output
....
 Your cdk8s Go project is ready!
   cat help      Prints this message  
   cdk8s synth   Synthesize k8s manifests to dist/
   cdk8s import  Imports k8s API objects to "imports/k8s"
  Deploy:
   kubectl apply -f dist/

Обновите сгенерированный файл go.mod и замените его следующим — это упростит вам задачу.

Не стесняйтесь использовать последнюю версию модулей, если это необходимо.

module cdk8s-crd
go 1.16
require (
    github.com/aws/constructs-go/constructs/v10 v10.1.42
    github.com/aws/jsii-runtime-go v1.61.0
    github.com/cdk8s-team/cdk8s-core-go/cdk8s/v2 v2.3.34
)

Для начала давайте поработаем с очень (очень!) простым определением пользовательского ресурса.

Я буду использовать образец CRD из примера Kubernetes. Если честно, то особо ничего не делает. Но, поскольку мы только начинаем, этого должно быть достаточно!

Сначала установите/зарегистрируйте сам ресурс CRD:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/sample-controller/master/artifacts/examples/crd.yaml

Подтвердите, был ли установлен CRD:

kubectl get crd
# output
NAME                           CREATED AT
foos.samplecontroller.k8s.io   2022-07-08T09:28:46Z
kubectl get foos.samplecontroller.k8s.io
#output (as expected)
No resources found in default namespace.

Итак, мы только что установили CRD с именем foos.samplecontroller.k8s.io и типом Foo. Можно создать экземпляр этого, используя yaml... но...

Мы здесь, чтобы писать код Go!

Для этого сначала импортируйте CRD как API, используя cdk8s — это автоматически создаст соответствующие представления Go API (structs и т. д.):

cdk8s import https://raw.githubusercontent.com/kubernetes/sample-controller/master/artifacts/examples/crd.yaml

Проверьте каталог imports, должна быть создана дополнительная папка.

imports/
└── samplecontrollerk8sio
    ├── internal
    │   └── types.go
    ├── jsii
    │   ├── jsii.go
    │   └── samplecontrollerk8sio-0.0.0.tgz
    ├── samplecontrollerk8sio.go
    ├── samplecontrollerk8sio.init.go
    └── version

Теперь мы можем использовать CRD так же, как любой другой ресурс/API Kubernetes (например, Deployment), и импортировать его в код cdk8s Go. Создайте новый файл с именем foo.go и скопируйте следующий код:

package main
import (
    "cdk8s-crd/imports/samplecontrollerk8sio"
    "github.com/aws/constructs-go/constructs/v10"
    "github.com/aws/jsii-runtime-go"
    "github.com/cdk8s-team/cdk8s-core-go/cdk8s/v2"
)
type FooChartProps struct {
    cdk8s.ChartProps
}
func NewFooChart(scope constructs.Construct, id string, props *FooChartProps) cdk8s.Chart {
    var cprops cdk8s.ChartProps
    if props != nil {
        cprops = props.ChartProps
    }
    chart := cdk8s.NewChart(scope, jsii.String(id), &cprops)
    samplecontrollerk8sio.NewFoo(chart, jsii.String("foo1"), &samplecontrollerk8sio.FooProps{Spec: &samplecontrollerk8sio.FooSpec{DeploymentName: jsii.String("foo1-dep"), Replicas: jsii.Number(2)}})
    return chart
}

Посмотрите, как мы создали экземпляр samplecontrollerk8sio.Foo:

  • Импортирован автоматически созданный CRD API из пакета cdk8s-crd/imports/samplecontrollerk8sio,
  • Использовал функцию NewFoo и предоставил метаданные через FooProps

Замените содержимое main.go следующим:

package main
import (
    "github.com/cdk8s-team/cdk8s-core-go/cdk8s/v2"
)
type MyChartProps struct {
    cdk8s.ChartProps
}
func main() {
    app := cdk8s.NewApp(nil)
    NewFooChart(app, "FooApp", nil)
    app.Synth()
}

Все, что мы делаем, это включаем Chart, который мы только что определили (в foo.go), и включаем его в cdk8s App.

Чтобы создать ресурс Foo...

Запустите cdk8s synth — в результате появится манифест в папке dist:

apiVersion: samplecontroller.k8s.io/v1alpha1
kind: Foo
spec:
  deploymentName: foo1-dep
  replicas: 2
metadata:
  name: fooapp-foo1-c80094ac

Чтобы создать его в Kubernetes:

kubectl apply -f dist

Вы можете подтвердить, запустив:

kubectl get foo
kubectl get foos.samplecontroller.k8s.io

Чтобы продолжить самоанализ, вы можете использовать имя созданного ресурса, например. kubectl describe foo/fooapp-foo1-c80094ac

Хорошо, теперь, когда вы увидели простой пример, мы можем перейти к чему-то более сложному.

Настройте Kafka на Kubernetes с помощью Strimzi, cdk8s и Go

Strimzi — это проект CNCF с открытым исходным кодом, один из моих любимых! Если вы не знаете о Стримзи, ничего страшного. Достаточно понять, что он предоставляет способ запуска Apache Kafka в Kubernetes с помощью пользовательских определений ресурсов и соответствующих операторов для таких компонентов, как кластер Kafka, тема Kafka Connect, пользователи, зеркало Kafka и т. д.

Вот общая диаграмма того, как взаимодействуют различные компоненты Strimzi. Поскольку глубокое погружение Стримзи выходит за рамки, я бы порекомендовал вам обратиться к его (отличной!) документации за подробностями.

Как и прежде, нам нужно сначала установить сам CRD (также можно обратиться к Быстрому старту Стримзи)

kubectl create namespace kafka
kubectl create -f 'https://strimzi.io/install/latest?namespace=kafka' -n kafka
# wait for the Operator Pod to start up (Running)
kubectl get pod -n kafka --watch

Вы также можете проверить журналы оператора, используя kubectl logs deployment/strimzi-cluster-operator -n kafka -f

Каждый поддерживаемый компонент Kafka (кластер, подключение, пользователь и т. д.) имеет соответствующее определение пользовательского ресурса — для целей этого сообщения в блоге мы просто будем использовать CRD кластера Kafka и темы. Давайте импортируем их как API:

cdk8s import https://raw.githubusercontent.com/strimzi/strimzi-kafka-operator/main/install/cluster-operator/040-Crd-kafka.yaml
cdk8s import kafkatopic:=https://raw.githubusercontent.com/strimzi/strimzi-kafka-operator/main/install/cluster-operator/043-Crd-kafkatopic.yaml

Обратите внимание, что я добавил kafkatopic к имени модуля для темы CRD Kafka

Проверьте папку imports — вы должны увидеть две дополнительные папки с именами kafkastrimziio и kafkatopic_kafkastrimziio.

Снова пришло время для некоторого кода Go:

Создайте файл kafka_strimzi.go и скопируйте код из репозитория Github:

Или вы также можете просто сделать это: curl -o kafka.go https://raw.githubusercontent.com/abhirockzz/cdk8s-for-go-developers/master/part3-crd/kafka_strimzi.go

Здесь я проведу вас через важные части кода. Начните с функции NewKafkaChart, которая создает новый файл Chart.

func NewKafkaChart(scope constructs.Construct, id string, props *KafkaChartProps) cdk8s.Chart {
    //.... ommitted for brevity
    chart := cdk8s.NewChart(scope, jsii.String(id), &cprops)

Посмотрите, как кластер Kafka определяется с помощью структуры kafkastrimziio.KafkaProps (для более подробного изучения каждого из этих компонентов вы можете обратиться к Документации Strimzi). Мы указываем версию Kafka, количество узлов/реплик (мы будем придерживаться реплики с одним узлом), как выставить кластер и т. д.

//....
&kafkastrimziio.KafkaProps{
            Spec: &kafkastrimziio.KafkaSpec{
                Kafka: &kafkastrimziio.KafkaSpecKafka{
                    Version:  jsii.String("3.2.0"),
                    Replicas: jsii.Number(1),
                    Listeners: &[]*kafkastrimziio.KafkaSpecKafkaListeners{
                        {
                            Name: jsii.String("plain"),
                            Port: jsii.Number(9092),
                            Type: kafkastrimziio.KafkaSpecKafkaListenersType_INTERNAL,
                            Tls:  jsii.Bool(false),
                        },
                    },
//....

Затем мы добавляем необходимую конфигурацию для кластера Kafka (в связи с тем, что у нас есть только кластер с одним узлом), а также хранилище (для этого примера подойдет эфемерное хранилище).

//...
Config: map[string]interface{}{
                        "offsets.topic.replication.factor":         1,
                        "transaction.state.log.replication.factor": 1,
                        "transaction.state.log.min.isr":            1,
                        "default.replication.factor":               1,
                        "min.insync.replicas":                      1,
                        "inter.broker.protocol.version":            "3.2",
                    },
                    Storage: &kafkastrimziio.KafkaSpecKafkaStorage{
                        Type: kafkastrimziio.KafkaSpecKafkaStorageType_EPHEMERAL,
                    },
//...

Наконец, мы настраиваем Zookeeper, а также Entity operator, который обрабатывает топики Kafka (а также пользователей, хотя мы его здесь не используем)

//...
Zookeeper: &kafkastrimziio.KafkaSpecZookeeper{
                    Replicas: jsii.Number(1),
                    Storage: &kafkastrimziio.KafkaSpecZookeeperStorage{
                        Type: kafkastrimziio.KafkaSpecZookeeperStorageType_EPHEMERAL,
                    },
                },
                EntityOperator: &kafkastrimziio.KafkaSpecEntityOperator{
                    TopicOperator: &kafkastrimziio.KafkaSpecEntityOperatorTopicOperator{},
                }}})
//...

Чтобы подключить его, обновите файл main.go:

func main() {
    app := cdk8s.NewApp(nil)
    //NewFooChart(app, "FooApp", nil)
    NewKafkaChart(app, "KafkaApp", nil)
    app.Synth()
}

Чтобы создать кластер Kafka с помощью CRD…

Следуйте обычному рабочему процессу:

# generate manifest (check it in dist folder)
cdk8s synth
# apply it (note the kafka namespace)
kubectl apply -f dist/ -n kafka

Подождите, пока ресурсы будут созданы:

KAFKA_CRD_INSTANCE_NAME=$(kubectl get kafka -n kafka -o=jsonpath='{.items[0].metadata.name}')
kubectl wait kafka/$KAFKA_CRD_INSTANCE_NAME --for=condition=Ready --timeout=300s -n kafka

После создания всех ресурсов кластера Kafka вы должны увидеть следующее сообщение — kafka.kafka.strimzi.io/<name of your Kafka CRD instance> condition met. Теперь кластер Kafka готов, и мы можем протестировать его, используя старый добрый производитель и потребитель Kafka на основе CLI (инструкции в кратком руководстве Strimzi).

BOOSTRAP_SERVER=$(kubectl get kafka -n kafka -o=jsonpath='{.items[0].metadata.name}')-kafka-bootstrap
kubectl -n kafka run kafka-producer -ti --image=quay.io/strimzi/kafka:0.29.0-kafka-3.2.0 --rm=true --restart=Never -- bin/kafka-console-producer.sh --bootstrap-server $BOOSTRAP_SERVER:9092 --topic test-topic
kubectl -n kafka run kafka-consumer -ti --image=quay.io/strimzi/kafka:0.29.0-kafka-3.2.0 --rm=true --restart=Never -- bin/kafka-console-consumer.sh --bootstrap-server $BOOSTRAP_SERVER:9092 --topic test-topic --from-beginning

Это пока все!

Время подводить итоги

Вы узнали, как объединить определение пользовательского ресурса Kubernetes с cdk8s. Это действительно мощно и означает, что вы можете продолжать использовать код (в данном случае написанный на Go) для определения встроенных ресурсов Kubernetes (таких как Deployments и т. д.), а также пользовательских ресурсов!

Вам понравилось то, что вы пробовали?

Что ж, можно продолжать учиться!

Несколько предложений:

  1. Вы можете попробовать обновить существующий код, чтобы добавить ресурс Deployment, который ссылается на клиентское приложение Kafka (сначала его нужно написать и упаковать как контейнер Docker) и получить доступ к созданному вами кластеру Kafka. Узнайте, как получить параметры подключения.
  2. Созданный нами кластер Kafka был настроен только на внутренний доступ. Изучите варианты, чтобы выставить его извне (обратитесь к документации Strimzi) и обновите код, чтобы реализовать это (должно быть небольшое изменение). На какие объекты Kubernetes это повлияет?

Удачного кодирования!