[Перевод] Напишите свою инфраструктуру Kubernetes — как код Go

yd1vhz1mhy3_dnlklsstns7ffrm.png


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

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

Я буду предполагать, что вы обладаете некоторыми знаниями Kubernetes Custom Resource Definitions и, возможно, даже использовали некоторые из них в виде операторов. Но даже если это не так, то всё в порядке! В документации Kubernetes они достаточно хорошо описаны. Вы всегда можете изучить её, вернуться и продолжить чтение!


cdk8s позволяет использовать объекты Kubernetes API непосредственно в коде, без необходимости импорта отдельных клиентских пакетов Go, благодаря cdk8s import. (также упомянутого в разделе «Wait, what about the Kubernetes API dependencies?»предыдущего поста). Но также можно использовать его для Custom Resource Definitions! Давайте посмотрим, как это происходит.

▍ Перед началом работы


Вам необходимо установить 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


▍ Для начала давайте поработаем с очень (очень!) простым Custom Resource Definition


Я воспользуюсь примером 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 — это автоматически создаст соответствующие обозначения (struct и т. д.):

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) и импортировать его в код Go cdk8s. Создадим новый файл 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) и включили его в App cdk8s.

▍ Создание ресурса 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, один из моих любимых! Вполне нормально, если вы не знаете о Strimzi. Достаточно понимать, что он предоставляет способ запуска Apache Kafka на Kubernetes при помощи Custom Resource Definitions и соответствующих операторов для таких компонентов, как кластер Kafka, тема Kafka Connect, пользователи, Kafka Mirror и т. п.

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

yd1vhz1mhy3_dnlklsstns7ffrm.png


Как и раньше, сначала нам нужно установить CRD (также можно сверяться с Strimzi Quickstart).

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


Также можно проверять логи Operator при помощи kubectl logs deployment/strimzi-cluster-operator -n kafka -f


Каждый поддерживаемый компонент Kafka (кластер, connect, пользователь и так далее) имеет соответствующее Custom Resource Definition. В этом посте мы будем использовать только 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


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


Проверьте папку 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 {
    //.... опущено для краткости
    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, обрабатывающий темы 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/ condition met. Теперь кластер Kafka готов и мы можем протестировать его при помощи старых добрых издателя и потребителя Kafka CLI (инструкции можно найти в Strimzi Quickstart).

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 Custom Resource Definition c cdk8s. Это очень мощная система, позволяющая продолжать использование кода (в данном случае написанного на Go) для определения встроенных ресурсов Kubernetes (например, Deployment и так далее), а также Custom Resources!

▍ Вам понравилось?


Можно продолжить обучение! Вот пара предложений:

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


sz7jpfj8i1pa6ocj-eia09dev4q.png

© Habrahabr.ru