![](https://habrastorage.org/getpro/habr/upload_files/2ed/ee2/be5/2edee2be5212c5e896b6ac2a69c37dbd.png)
Всем привет. Меня зовут Путилин Дмитрий (Добрый Кот) Telegram.
От коллектива FR-Solutions и при поддержке @irbgeo Telegram : Продолжаем серию статей о K8S.
В этой статье мы поделимся своим опытом разработки Managed K8S под Yandex Cloud и расскажем, как мы создали конфигурацию, которую можно легко адаптировать для запуска в любом облаке или on-premises решении, изменяя только некоторые настройки. Если вы заинтересованы в построении гибких и масштабируемых Kubernetes-кластеров, то этот материал обязательно для вас.
В предыдущих статьях
Базовая организации сертификатов в kubeadm — Сертификаты K8S или как распутать вермишель Часть 1.
Как начать использовать внешний PKI сторедж Vault для хранения и выписывания сертификатов для k8s control‑plane — Сертификаты K8S или как распутать вермишель Часть 2.
Как развернуть Kubernetes кластер по принципу Hard Way — Kubernetes the hard way.
Проблема
Из моего личного опыта могу сказать, что Managed решения в облаках или в онпрем‑серверах — это отличный инструмент для создания своего продукта, и зачастую этого достаточно. Однако, бывают ситуации, когда нужно больше гибкости и возможностей настройки, а Managed решение предоставляет ограниченный набор функций.
Для нас было критично использовать сетевой плагин Cilium с нашими настройками, также нам требовались флаги feature‑gates, которых по дефолту нет в Yandex K8S API.
В принципе, не беда, мы всегда можем развернуть стационарный K8S и закастомизировать его как нам угодно. Возникли следующие вопросы: какие инструменты взять, какой выстроить процесс и как сделать так, что бы создаваемые кластера были одинаковыми?
Выбор инструментов
Для данной задачи однозначно требуются cloud native инструменты, поэтому выбор пал на Terraform. Остались вопросы: как настраивать узлы, нужен ли нам Ansible, Puppet, SaltStack? После 3 месяцев поиска золотой пилюли мы поняли, что для создания кластера нам потребуется только Terraform и cloud‑init.
Архитектура
Так как в основе нашего продукта лежит Terraform, то одно из условий работы с ним — Сервисно‑ресурсная модель (СРМ).
Ресурсами выступают все его компоненты, от балансировщика нагрузки до конфигураци cloud‑init для нашего кластера.
Также CPM позволяет менять одинаковые типы ресурсов без потребности в смене процесса деплоя кластера, таким образом, описав модули создания инфраструктуры под Yandex Cloud, VK Cloud и т. п., и, поменяв намеример модуль Yandex cloud на модуль VK Cloud, получим тот же результат, но в другом окружении.
![](https://habrastorage.org/getpro/habr/upload_files/5f6/365/654/5f6365654bef90b37098c7b84eea2afb.png)
Сертификаты
Наиболее значимым и сложным этапом было разработать подход работы с сертификатами, проблема была упомянута в предыдущих статьях. Мы определили основные спецификации для сертификатов и описали ресурсы Vault, которые создаются на основе содержимого спецификации. Однако возник вопрос доставки ключей/токенов на мастер-узлы, чтобы клиент на узле мог запросить сертификаты, указанные в спецификации. Было рассмотрено несколько вариантов решения этой проблемы:
Для получения secret_id и role_id от Approle можно использовать временный токен, который имеет ограниченный доступ. Для этого токен должен иметь достаточно длительный срок жизни, чтобы виртуальная машина успела запустить клиента, или можно указать, что использование токена допустимо только один раз.
Использование сервиса IAM от облачного провайдера для сохранения secret_id/role_id для каждой машины в облаке. Затем, можно использовать cloud-cli для получения необходимых секретов прямо с хоста.
Мы предпочли второй вариант и выбрали его, так как он лучше подходил для нашего случая. Однако первый вариант может быть полезен в тех облаках, где нет поддержки сервиса IAM.
Переменные окружения
При написании кода мы поняли, что описывать каждый модуль с его входными и выходными переменными - это трудоемкий процесс, особенно когда возникают повторы. Через некоторое время мы решили, что имеет смысл выделить отдельный модуль, содержащий переменные, которые используются в нескольких модулях. Таким образом, мы смогли уменьшить объем входных аргументов каждого модуля и привести их к более компактному формату: каталог
variable "k8s_global_vars" {
description = "module:K8S-GLOBAL VARS"
type = any
default = {}
}
При создании структуры этого модуля мы также уделяли внимание принципу "записал - забыл" - это означает, что если мы хотим добавить только переменную, но нехотим добавлять соответствующий вывод в OUTPUT, нам нужно использовать структурные массивы, в которые мы добавляем только нужные нам переменные, а глобальный вывод остается единым на блок. Например:
locals {
k8s-addresses = {
local_api_address = format("%s.1", join(".", slice(split(".",local.k8s_network.service_cidr), 0, 3)) )
dns_address = format("%s.10", join(".", slice(split(".",local.k8s_network.service_cidr), 0, 3)) )
idp_provider_fqdn = format("auth.%s" , local.cluster_metadata.base_domain)
base_cluster_fqdn = format("%s.%s" , local.cluster_metadata.cluster_name, local.cluster_metadata.base_domain)
wildcard_base_cluster_fqdn = format("%s.%s.%s", "*" , local.cluster_metadata.cluster_name, local.cluster_metadata.base_domain)
etcd_server_lb_fqdn = format("%s.%s.%s", "etcd" , local.cluster_metadata.cluster_name, local.cluster_metadata.base_domain)
}
}
output "k8s-addresses" {
value = local.k8s-addresses
}
Генерация cloud-init конфигурации является не менее важным аспектом, поскольку эта конфигурация передается виртуальной машине при ее создании.
В первых версиях мы были вынуждены описывать каждый файл, создавать шаблоны для них и выносить их в отдельные модули по логическому смыслу, например, модуль containerd" включал в себя конфигурационные файлы и шаблоны для systemd сервисов. Однако, такой подход был слишком трудоемким в поддержке из-за большого количества модулей.
Мы решили использовать подход, подобный kubeadm. Сначала мы попытались развернуть кластер с помощью kubeadm, но выяснилось, что он не может выполнить первоначальную настройку системы, такую как установка пакетов, добавление конфигурационных файлов и запуск сервисов. Поэтому мы начали разработку инструмента, который бы мог настроить систему до требуемого состояния. Результатом этой работы стал fraimctl - инструмент, который заменил множество шаблонов одной командой fraimctl init
.
Таким образом, нам оставалось описать:
базовый конфиг fraimctl (устанавливает все компоненты и готовит конфиги к ним);
базовый конфиг kubeadm (генерит статик под манифесты и чекает, что кластер поднят);
базовый конфиг key-keeper (клиент который запрашивает сертификаты).
У нас есть несколько задач, которые мы должны выполнить, чтобы полностью отказаться от kubeadm. Мы планируем перенести этап создания конфигурационных файлов key-keeper, kubeconfig и static pod manifests в fraimctl. Кроме того, мы добавим функционал для проверки готовности сертификатов и кластера, а также этап маркировки узлов. Это позволит нам полностью отказаться от использования kubeadm и не зависеть от этого инструмента.
Как уже упоминалось ранее, этот инструмент создан для возможности полного отказа от использования kubeadm и настройки кластеров без его использования.
Пример конфигурациооного файла:
fraimctl.conf
- apiVersion: fraima.io/v1alpha
kind: Containerd
spec:
service:
extraArgs:
# This document provides the description of the CRI plugin configuration.
# The CRI plugin config is part of the containerd config
# Default: /etc/containerd/config.toml
config: /etc/kubernetes/containerd/config.toml
configuration:
extraArgs:
version: 2
plugins:
io.containerd.grpc.v1.cri:
containerd:
runtimes:
runc:
# Runtime v2 introduces a first class shim API for runtime authors to integrate with containerd.
# The shim API is minimal and scoped to the execution lifecycle of a container.
runtime_type: "io.containerd.runc.v2"
options:
# While containerd and Kubernetes use the legacy cgroupfs driver for managing cgroups by default,
# it is recommended to use the systemd driver on systemd-based hosts for compliance of the "single-writer" rule of cgroups.
# To configure containerd to use the systemd driver, set the following option:
SystemdCgroup: true
downloading:
- name: containerd
src: https://github.com/containerd/containerd/releases/download/v1.6.6/containerd-1.6.6-linux-amd64.tar.gz
checkSum:
src: https://github.com/containerd/containerd/releases/download/v1.6.6/containerd-1.6.6-linux-amd64.tar.gz.sha256sum
type: "sha256"
path: /usr/bin/
owner: root:root
permission: 0645
unzip:
status: true
files:
- bin/containerd
- bin/containerd-shim
- bin/containerd-shim-runc-v1
- bin/containerd-shim-runc-v2
- bin/containerd-stress
- bin/ctr
- name: runc
src: https://github.com/opencontainers/runc/releases/download/v1.1.3/runc.amd64
path: /usr/bin/
owner: root:root
permission: 0645
starting:
- systemctl enable containerd
- systemctl start containerd
Каждый компонент имеет четыре стадии:
downloading (загружает бинарные файлы, проверяет контрольные суммы, распаковывает необходимые компоненты и размещает их в соответствующих папках.)
service (генерирует службу systemd, и с помощью параметра extraArgs можно настроить ее поведение под свои нужды.)
configuration (генерирует конфигурацию для службы systemd, и с помощью параметра extraArgs можно настроить ее поведение под свои нужды.)
starting (выполняет необходимые команды после первых трех этапов.)
Одной из ключевых особенностей этого инструмента является этап загрузки (Downloading), который загружает бинарные файлы компонентов. Это позволяет не зависеть от производителя операционной системы и разворачивать единым подходом на любом хосте, не нужно думать о множестве условий (if else) и о том какая операционная система в основе.
Также предусмотрены отдельные конфигурационные файлы для настройки sysctl и modprobe.
fraimctl.conf
- apiVersion: fraima.io/v1alpha
kind: Sysctl
spec:
configuration:
extraArgs:
net.ipv4.ip_forward: 1
starting:
- sudo sysctl --system
- apiVersion: fraima.io/v1alpha
kind: Modprob
spec:
configuration:
extraArgs:
- br_netfilter
- overlay
starting:
- sudo modprobe overlay
- sudo modprobe br_netfilter
- sudo sysctl --system
Инфраструктура
![](https://habrastorage.org/getpro/habr/upload_files/038/591/d7a/038591d7a140c7d3a4bc0d1b82654cb5.png)
Каждый кубик в Terraform представляет собой ресурс и логически определяется как класс в языке программирования. Мы можем определить класс, например, loadBalancer, который принимает определенный набор аргументов и возвращает структуру, которая также заранее определена. Это означает, что мы можем изменять кубики по нашему усмотрению, а при смене облака все компоненты будут взаимодействовать друг с другом благодаря структуре входных и выходных параметров.
Благодаря этой архитектуре мы можем обновлять операционные системы без проблем и даже менять производителя операционной системы на лету.
kubectl get no -o wide
root@master-2-cluster-2:/home/dkot# kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
master-1-cluster-2 Ready control-plane,master 2m50s v1.23.12 10.1.0.11 51.250.66.122 Ubuntu 20.04.4 LTS 5.4.0-124-generic containerd://1.6.8
master-2-cluster-2 Ready control-plane,master 2m53s v1.23.12 10.2.0.33 84.201.139.95 Ubuntu 20.04.4 LTS 5.4.0-124-generic containerd://1.6.8
master-3-cluster-2 Ready control-plane,master 2m55s v1.23.12 10.3.0.21 51.250.40.244 Ubuntu 20.04.4 LTS 5.4.0-124-generic containerd://1.6.8
root@master-2-cluster-2:/home/dkot# kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
master-1-cluster-2 Ready control-plane,master 37s v1.23.12 10.1.0.12 62.84.119.244 Debian GNU/Linux 10 (buster) 4.19.0-18-amd64 containerd://1.6.8
master-2-cluster-2 Ready control-plane,master 12m v1.23.12 10.2.0.16 51.250.27.187 Debian GNU/Linux 10 (buster) 4.19.0-18-amd64 containerd://1.6.8
master-3-cluster-2 Ready control-plane,master 12m v1.23.12 10.3.0.13 51.250.45.49 Debian GNU/Linux 10 (buster) 4.19.0-18-amd64 containerd://1.6.8
Для каждого облака необходимо написать модуль, который повторяет структуру выше, чтобы у нас всегда была одинаковая архитектура на всех кластерах.
Реализация
Давайте рассмотрим базовый проект и то, как можно начать использовать этот инструмент.
Скачиваем репозиторий https://github.com/fraima/kubernetes
В этом репозитории есть несколько разделов
infrastructure-vault (создает рут PKI в Vault)
infrastructure-yandex (создает базовую конфигурацию в YC, которая включает в себя создание VPC, таблицы маршрутизации и создание сервисных аккаунтов по умолчанию)
infrastructure-keycloak (устанавливает базовую конфигурацию для Keycloak, которая позволяет авторизоваться в кластере через этот инструмент)
k8s-yandex-cluster (проект-шаблон, который используется для создания кластера.)
Заходим в каждый раздел по очереди и применяем, что прописано в Readme.
Подготовка
Для начала работы вам понадобятся переменные для подключения к Vault, YC и Keycloak.
environments
export TF_VAR_YC_CLOUD_ID=""
export TF_VAR_YC_FOLDER_ID=""
export TF_VAR_YC_TOKEN=""
export TF_VAR_YC_ZONE=""
export TF_VAR_VAULT_TOKEN=""
export TF_VAR_VAULT_ADDR=""
export TF_VAR_KEYCLOAK_REALM=""
export TF_VAR_KEYCLOAK_CLIENT_ID=""
export TF_VAR_KEYCLOAK_USER=""
export TF_VAR_KEYCLOAK_PASSWORD=""
export TF_VAR_KEYCLOAK_URL=""
Этот подход позволяет использовать Terraform в контейнере через инструмент CI/CD, не указывая реальные значения переменных в провайдерах.
Если вы работаете с чистым Terraform, не забывайте выделять каждый кластер в отдельный workspace.
terraform workspace new example
terraform plan -var-file vars/example.tfvars
terraform apply -var-file vars/example.tfvars
terraform destroy -var-file vars/example.tfvars
Инит конфиг
Основная конфигурация зависит от двух файлов в проекте.
locals.defaults.tf - базовые значения, которые определены для всех наших кластеров.
vars/${cluster_name}.tf - переменные, которые специально указаны для конкретного кластера.
vars/${cluster_name}.tf
global_vars = {
cluster_name = "example"
pod_cidr = "10.102.0.0/16"
serviceaccount_k8s_controllers_name = "yandex-k8s-controllers"
kube_apiserver_flags = {
oidc-issuer-url = "https://auth.dobry-kot.ru/auth/realms/master"
oidc-client-id = "kubernetes-clusters"
oidc-username-claim = "sub"
oidc-groups-claim = "groups"
oidc-username-prefix = "-"
}
kube_controller_manager_flags = {
cluster-name = "kubernetes"
}
kube_scheduler_flags = {
}
addons = {
cilium = {
enabled = true
extra_values = {
cluster = {
name = "example"
id = 12
}
}
}
vault-issuer = {
enabled = true
extra_values = {}
}
coredns = {
enabled = true
extra_values = {}
}
gatekeeper = {
enabled = true
extra_values = {}
}
certmanager = {
enabled = true
extra_values = {}
}
machine-controller-manager = {
enabled = true
extra_values = {}
}
yandex-cloud-controller = {
enabled = true
extra_values = {}
}
yandex-csi-controller = {
enabled = true
extra_values = {}
}
compute-instance = {
enabled = true
custom_values = {
subnet_id = "e9bndv0b3c5asheadg09"
zone = "ru-central1-a"
image_id = "fd8ingbofbh3j5h7i8ll"
replicas = 1
}
extra_values = {
metadata = {
nodeLabels = {
"node-role.kubernetes.io/worker" = ""
"provider" = "yandex"
}
cloudLabels = {
tair = "critical"
}
}
}
}
}
}
cloud_metadata = {
cloud_name = "cloud-uid-vf465ie7"
folder_name = "example"
}
master_group = {
name = "master"
count = 3
default_subnet = "10.0.0.0/24"
default_zone = "ru-central1-a"
metadata = {
# user_data_template = "fraima-hbf"
user_data_template = "fraima"
}
}
Этот ENV-параметр дает возможность изменить значения, которые будут использованы в конфигурационных файлах или ресурсах в будущем.
Например, мы можем изменить или добавить флаги Kube-apiserver с помощью переменной "kube_apiserver_flags".
В файле с переменными на данный момент определены три группы.
"master_group" определяет, какие мастера следует заказать, в какой подсети они будут находиться, в какой зоне, будут ли они в разных зонах или нет, а также количество мастер-нод (это значение можно определить только один раз, изменить его с 1 на 3 в настоящее время невозможно).
"global_vars" определяет будущую конфигурацию кластера, включая его имя, подсети для подов, флаги для компонент, которые будут использоваться, а также какие аддоны будут добавлены.
"cloud_metadata" содержатся указатели на облачный провайдер, такие как cloud_name" и "folder_name".
Запускаем
time terraform apply -var-file vars/example.tfvars -auto-approve
По умолчанию будет развернут кластер с тремя мастер-нодами, каждая из которых имеет 6 CPU, 12 ГБ оперативной памяти и 100 ГБ дискового пространства, а также 10 ГБ для ETCD.
Для каждого кластера будет создан внешний балансер, к которому вы сможете подключиться. Также будут созданы аддоны, которые настроят сеть, базовые интеграции с YC, такие как CSI driver, Cloud Controller и Machine Controller Manager для заказа воркер-нод в облаке.
Через шесть минут вы получите полностью готовый кластер и инструкции о том, как подключиться к нему.
Apply complete! Resources: 86 added, 0 changed, 0 destroyed.
Outputs:
LB-IP = "kubectl config set-cluster cluster --server=https://158.160.63.64:443 --insecure-skip-tls-verify"
real 6m4,698s
user 0m24,182s
sys 0m1,582s
dk@dobry-kot-system:~/workspace/fraima/kubernetes/k8s-yandex-cluster-naked$ kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
master-50d858e0-1 Ready control-plane,master 9m34s v1.23.12 10.0.0.12 158.160.51.95 Ubuntu 20.04.4 LTS 5.4.0-124-generic containerd://1.6.6
master-50d858e0-2 Ready control-plane,master 9m33s v1.23.12 10.0.0.6 158.160.38.139 Ubuntu 20.04.4 LTS 5.4.0-124-generic containerd://1.6.6
master-50d858e0-3 Ready control-plane,master 9m34s v1.23.12 10.0.0.19 158.160.42.200 Ubuntu 20.04.4 LTS 5.4.0-124-generic containerd://1.6.6
Вы можете заметить, что кластер успешно запущен и функционирует. Кроме того, у узлов теперь есть внешние IP-адреса и свидетельствует о том, что интеграция с YC работает.
Если вы используете keycloak для подключения, не забудьте установить плагин "kubectl login" и воспользоваться универсальным kubeconfig.
kubeconfig
apiVersion: v1
clusters:
- cluster:
insecure-skip-tls-verify: true
server: https://158.160.63.64:443
name: cluster
contexts:
- context:
cluster: cluster
namespace: kube-fraima-machine-controller-manager
user: cluster
name: cluster
current-context: cluster
kind: Config
preferences: {}
users:
- name: cluster
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- oidc-login
- get-token
- --oidc-issuer-url=https://$KEYCLOAK-SERVER/auth/realms/master
- --oidc-client-id=kubernetes-clusters
- --oidc-client-secret=kube-client-secret
- --certificate-authority=/usr/local/share/ca-certificates/oidc-ca.pem
- --skip-open-browser
- --grant-type=password
- --username=$USERNAME
- --password=$PASSWORD
command: kubectl
env:
- name: context
value: $(kubectl config current-context)
interactiveMode: IfAvailable
provideClusterInfo: false
Наполнение
NAME STATUS AGE
default Active 11m
kube-fraima-certmanager Active 8m48s # CERTMANAGER
kube-fraima-dns Active 9m57s # COREDNS
kube-fraima-machine-controller-manager Active 8m55s
kube-fraima-opa Active 9m44s # GATEKEEPER
kube-fraima-sdn Active 10m # CILIUM
kube-fraima-yandex-cloud-controller Active 11m
kube-fraima-yandex-csi-controller Active 9m54s
kube-node-lease Active 11m
kube-public Active 11m
kube-system Active 11m
Внимание
Одной из важных особенностей этих кластеров является отсутствие приватных ключей от СА на мастерах, так как они хранятся в VAULT. Однако, такой подход приводит к определенным проблемам.
Вы можете добавить любую ноду в кластер через csr bootstraping, где нода генерирует запрос на сертификат и отправляет его в API, а затем вы подтверждаете этот запрос и нода получает свои сертификаты и добавляется в кластер. Однако, в данной инсталляции это нельзя сделать стандартными средствами.
Поскольку kube-controller-manager занимается выдачей сертификатов для узлов, то без доступа к приватному ключу CA этот функционал теряется. Однако, мы нашли способ получить сертификаты, установив Certmanager и Gatekeeper, а затем настроив ClusterIssuer в Certmanager для интеграции с VAULT. С помощью этого ClusterIssuer можно будет выписывать сертификаты только для worker/master узлов. Затем в Gatekeeper настраиваем мутацию ресурса CSR, который изменит базовый SIGNERNAME с "kubernetes.io/kubelet-serving" на "clusterissuers.cert-manager.io/vault-issuer". Таким образом, мы сможем получить необходимые сертификаты.
dk@dobry-kot-system:~/Downloads$ kubectl get csr
NAME AGE SIGNERNAME REQUESTOR REQUESTEDDURATION CONDITION
csr-52cx7 12m kubernetes.io/kubelet-serving system:node:master-50d858e0-3 <none> Pending
csr-lbhqf 12m kubernetes.io/kubelet-serving system:node:master-50d858e0-1 <none> Pending
csr-n27p4 12m kubernetes.io/kubelet-serving system:node:master-50d858e0-2 <none> Pending
node-csr-3l5VT-i7YinQWaTvbCY467d27GQLnSqnT_BYgk_PFII 8m17s clusterissuers.cert-manager.io/vault-issuer system:bootstrap:663273 <none> Pending
Как вы можете заметить, новый узел запросил сертификат через CSR, но SIGNERNAME у него установлен как "clusterissuers.cert-manager.io/vault-issuer". После подтверждения этого
запроса Certmanager выдаст сертификат, который будет храниться во внешнем хранилище Vault.
kubectl certificate approve node-csr-3l5VT-i7YinQWaTvbCY467d27GQLnSqnT_BYgk_PFII
После этого появится еще один запрос на сертификат, который также нужно подтвердить, и после этого узел будет добавлен в кластер.
dk@dobry-kot-system:~/Downloads$ kubectl get no -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
master-50d858e0-1 Ready control-plane,master 24m v1.23.12 10.0.0.12 158.160.51.95 Ubuntu 20.04.4 LTS 5.4.0-124-generic containerd://1.6.6
master-50d858e0-2 Ready control-plane,master 24m v1.23.12 10.0.0.6 158.160.38.139 Ubuntu 20.04.4 LTS 5.4.0-124-generic containerd://1.6.6
master-50d858e0-3 Ready control-plane,master 24m v1.23.12 10.0.0.19 158.160.42.200 Ubuntu 20.04.4 LTS 5.4.0-124-generic containerd://1.6.6
worker-yandex-compute-instance-68ffc-f2sd2 Ready worker 11m v1.23.12 10.154.0.11 51.250.72.216 Ubuntu 22.04.1 LTS 5.15.0-46-generic containerd://1.6.6
Планы
Расширить функционал Fraimctl, чтобы отказаться от использования Kubeadm.
Написать инфраструктурные модули для AWS и VK-Cloud.
Покрыть Terraform тестами.
Организовать модули более четко и удалить ненужное.
Перейти с использования Terraform + Helm на Terraform + Flux.
Написать расширение для K8S API для добавления нашего кастомного функционала.
Добавить инструмент для настройки узлов как Day2 операций.
У нашего коллектива амбициозные планы и мы нацелены на получение статуса CNCF.
Если вы оценили наш контент, присоединяйтесь к нашему чату, где вы сможете задать любые интересующие вас вопросы. Мы также будем рады любой помощи в нашем проекте.
Контакты
terraform modules: https://github.com/fraima/terraform-modules
terraform cluster: https://github.com/fraima/kubernetes
telegram community: https://t.me/fr_solution_ru
telegram me: https://t.me/Dobry_kot