[Перевод] Напишите свою инфраструктуру Kubernetes — как код Go
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 выходит за рамки нашей статьи, я рекомендую обратиться за подробностями к его (превосходной!) документации.
Как и раньше, сначала нам нужно установить 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/
. Теперь кластер 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!
▍ Вам понравилось?
Можно продолжить обучение! Вот пара предложений:
- Вы можете попробовать дополнять готовый код, чтобы добавлять ресурс
Deployment
, относящийся к клиентскому приложению Kafka (сначала вам нужно написать его и упаковать как контейнер Docker) и способный обеспечивать доступ к созданному вами кластеру Kafka. Изучите, как можно получать параметры соединения. - Созданный нами кластер Kafka сконфигурирован так, чтобы иметь только доступ Internal. Изучите опции, позволяющее раскрыть его наружу (см. документацию Strimzi) и дополните код, чтобы реализовать это (изменение должно быть небольшим). На какие объекты Kubernetes повлияет это изменение?