---
title: Начало работы с kubernetes
description: Как там это всё разворачивать в двух словах.
category: DevOps
position: 4
---

# Проблема в изучении кубернетес

Многие люди, кто задавались вопросом как начать работать с
кубернетесом сталкивались с тем, что документация крайне
большая сложная и нет нормального описания как
завернуть маленький проект из одного проекта в свой кластер или
как развернуть свой кластер без боли.

А всё потому что вся документация нацелена на большие
production-ready системы с большим rps и тому подобным.

В данной статье я попробую исправить это вселенское
недопонимание используя k3s, свой комплюктер и немного знаний по кодингу.

<div align="center">

![Intro image](/images/k3s_start/kube_intro.jpg)

</div>

# Что такое кубернетес и почему это лучше докера

Многие ребята, кто хорошо знаком с докером и его
возможностями могут задаваться таким вопросом.
Для тех кто в танке, напомню, что докер
имеет вариант запуска в режиме кластера.
Этот функционал называется docker swarm.
В целом, swarm отдалённо напоминает kubernetes,
так как в этом режиме докер, худо-бедно но умеет
автоскейлится и запускаться в кластере,
но это всё равно немного не то.

<b-message type="is-info" has-icon>
	Также замечу, что кубер активно развивается и поддерживается.
  огромным количество компаний. А вот docker swarm уже по-немногу
  умирает и документация по нему не то чтобы супер хороша.
</b-message>

По большей части кубернетес это система,
которая будет управлять вашими приложениями,
следить за их состоянием и помогать вам в их
конфигурации. Кубер умеет очень много, поэтому
все интересные способности я в этой статье не смогу осветить,
но самую базу попробую рассказать.

# Из чего состоит кубернетес

Так как я в этой статье хотел затронуть
совсем базовые и практические вещи, то рассматривать
мы будем только крайне полезные компоненты.

- Container
- Pod
- Deployment
- Service
- Ingress
- Namespace
- Secret
- ConfigMap

А теперь рассмотрим немного поподробнее.

## Container

Контейнеры не то чтобы часть специфичная для кубернетес.
С контейнерами вы можете быть знакомы из кучи систем.
В контексте кубера они не обладают никакими дополнительными
свойствами. Это ровно то же, что и контейнеры `containerd`
или те, с которыми вы возились с докером. Ничего нового.

Собираются контейнеры для кубернетеса ровно тем же образом,
что и для докера.

<b-message type="is-warning" has-icon>
  
  Важная ремарка. Кубер начиная с 2021 кубернетес не поддерживает
  докер как бэкенд. Теперь кубер будет общаться с 
  containerd напрямую. Это значит, что теперь перед использованием
  контейнеров собранных на локальной машине надо будет импортировать их
  в `containerd` используя `ctr image import`.

Как импортировать образы почитать можно в [этой статье](https://cwienczek.com/2020/06/import-images-to-k3s-without-docker-registry/).

</b-message>

## Pod

<div align="center">
  <img alt="Pods" style="width: 100%" src="/images/k3s_start/pods.svg">
</div>

Поды - это логически связанные группы контейнеров.
Это самая базовая еденица кубернетеса.

В поде находится от одного до множества контейнеров.
Интересная особенность пода в том, что все контейнеры
делят один сетевой адресс. Другими словами,
если у вас один из контейнеров открыл порт `3000`,
то другие контейнеры из пода эти порты использовать не смогут.

То есть, если вы хотите логически связанные приложения
поместить в под, то они могут ходить друг к другу через лупбек
адреса. Такие как `localhost` или `127.0.0.1`.

## Deployment

Поды это круто, но есть одно но. Данные объекты неизменяемые
и сам под скейлить вручную занятие сомнительное.

Конечно, никто не мешает создать вам вручную [ReplicaSet](https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/)
и самому крутить там нужные значения. Но в среднем
вам с этим возиться не очень то хочется.

<br>

Deployment нужна именно для описания подов и создания
некоторых ресурсов, нужных для скейлинга.
Также с помощью деплойментов можно делать откаты приложения через
механиз роллбеков. Я это рассматривать не буду. Тут только база.

## Service

Сервис - это ресурс, с помощью которого поды могут общаться
между собой. По факту сервис описывает, какие порты пода
открыты и перенаправляет трафик на них.

Представьте себе под, который просто проксирует весь трафик с одного
порта на какой-нибудь порт пода. Это и есть Service.

<div align="center">

![Service overview](/images/k3s_start/service%20overview.png)

</div>

Выглядит сервис примерно так как показано на кртинке выше.
Он направляет весь входной трафик с указанного порта на
порты подов.

Также сервис выступает как балансировщик.

<b-message type="is-info" has-icon>
  Если вы хотите сделать запрос от одного пода до дргого внутри кластера, то
  вам придётся использовать сервис. 
  <br>
  Если вы попробуете сделать запрос напрямую по IP пода, то у вас, конечно же,
  получится, но это довольно странная идея из-за того, что при пересоздании
  пода у него может обновится IP внутри кластера. Поэтому лучше использовать сервисы. Также они потребуются для ингресса.
</b-message>

## Ingress

Ингрессы это сервис описывающий куда пускать трафик,
который поступает снаружи кластера.

<div align="center">

![Ingress overview](/images/k3s_start/ingress-overview.png)

</div>

Принцип работы ингресса следующий:

Вы указываете хост ингресса и различные регексы пути.
В зависимости от входящего запроса ингресс выбирает в какой сервис
направить его и в какой порт.

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

## Namespace

Неймспейсы это логические разделители уровня доступа.

Я предпочитаю использовать разные неймспейсы под различные логические группы приложений.

Например, в моём кластере есть отдельные неймспейсы для каждого приложения.

Скажем, у меня есть проект, в котором есть база данных и веб сервер.
Я держу их в одном неймспейсе, который называется также, как и приложени.

А вот телеграм боты -- приложения достаточно лёгкие.
Поэтому у меня есть неймспейс со всеми телеграм ботами.

Эта штука позволяет просто лучше организовать свой кубернетес кластер.

Также неймспейсы очень полезны для ограничивания возможностей
конкретного пользователя.
Например, вы можете создать правило, которое будет разрешать пользователю
смотреть на поды в каком-то неймспейсе, но при этом ему нельзя будет
что либо изменять.

## Secret и ConfigMap

Данные ресурсы нужны только чтобы хранить
внутри кластера какую-нибудь информацию.

Например вы можете сохранить в ConfigMap
переменные среды и потом использовать их в подах в одном неймспейсе.

В секреты обычно кидают сертификаты или какие-нибудь ключи.
Но так как секреты не особо секретные принято использовать [Vault](https://www.vaultproject.io/docs/platform/k8s). Но так как эта статья затрагивает только
основы рассматривать развертку и настройку Vault мы не будем, ну и также HashiCorp
всё довольно подробно расписали сами.

# Как развернуть k8s у себя

Для локального кубера есть пара вариантов.

- k3s (Недоступен под Windows)
- minikube

На первый взгляд minikube может показаться лучшим вариантом.
И он действительно хорош тем, что его легко почистить после
своих экспериментов. Однако, там есть проблемы с ингрессами.
По факту они не работают и там надо окольными путями получать
адреса приложения.

<br>

k3s - это легковесная production-ready реализация k8s. Ingress у него работают
отлично, поэтому я буду использовать его.

<br>

Я не буду зацикливаться на установке `minikube`, так как
он прост в установке и первоначальной настройке. Почитать подробнее можно в
[официальном гайде от minikube](https://minikube.sigs.k8s.io/docs/start/).

С `k3s` всё немного посложнее, но тоже достаточно просто, если немного разобраться. Первоначальная установка описана в [официальной доке k3s](https://rancher.com/docs/k3s/latest/en/installation/install-options/).

## Подключение кластера

После установки в домашней дериктории должен был быть
сгенерирован файл `.kube/config`. Этот файл содержит данные для
подключения к различным кластерам. `minikube` Сам добавляет
ключи для подключения к .kube/config. `K3S` не изменяет
никаким образом `.kube/config`, поэтому надо будет это сделать вручную.

Для того, чтобы это сделать сначала разберёмся как выглядит конфиг.

```yaml{}[.kube/config]
apiVersion: v1
kind: Config
preferences: {}
# Массив кластеров.
# Каждый элемент -- данные для подключения
# Тут также есть названия для каждого кластера.
clusters:
- name: hyper
  cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://192.168.1.55:6443
- name: k3s-local
  cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://127.0.0.1:6443
# Массив данных пользователя.
# Тут указаны пользователи с
# различными сертификатами.
# Обычно для разных серверов у вас будут
# Различные данные для входа.
users:
- name: hyper-s3rius
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED
- name: k3s-user
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED
# Массив контекстов.
# Контекст - это связующее звено
# между кластероами и пользователями.
contexts:
- context:
    cluster: hyper
    user: hyper-s3rius
  name: hyper
- context:
    cluster: k3s-local
    user: k3s-user
  name: k3s
# Текущий контекст указывает какой
# контекст использовать по умолчанию.
current-context: "k3s"
```

Для работы с кубером из командной строки
можно использовать `kubectl`. Чтобы сменить контекст в любой команде вы
можете передать параметр `--context $CONTEXT_NAME`, где `$CONTEXT_NAME` это название контекста.

Чтобы достать данные для подключения к `k3s` надо посмотреть его конфиг
и скопировать данные. Либо выставить переменную среды, которая будет
указывать конфиг k3s. Конфиг подключения `k3s` лежит в файле `/etc/rancher/k3s/k3s.yaml`.

Можете выполнить команду, которая будет просить kubectl использовать указанный конфиг:
`export KUBE_CONFIG=/etc/rancher/k3s/k3s.yaml`

<br>

Либо скопируйте нужные данные для подключения себе в `.kube/config`.

После настройки выполните команду и проверьте что вам вернулось
что-то подобное. Это значит, что никаких ресурсов пока в
кластере нет. Это мы исправим позже. Пока что можно сказать,
что подключение прошло успешно.

```bash
$ kubectl --context "my-context" get pods
No resources found in default namespace.
```

Если же у вас выпадает ошибка, например такая.

```
The connection to the server localhost:8080 was refused - did you specify the right host or port?
```

То либо запустите кластер

```
sudo systemctl start k3s.service
```

Либо у вас неверные данные для входа.

### Мониторинг кластера

Для просматривания управления кластером используется cli утилита `kubectl`.
Но для того, чтобы с ней работать нужно получше понять что вообще в кластере есть.

Для этого я советую использовать [Lens](https://k8slens.dev/). Это крайне
удобный интерфейс для управления своим кластером.
Также там есть очень клёвая настрока, которая сама включит мониторинг
потребления памяти и процессора для всех подов и кластера в общем.

На локальной машине это не очень много смысла имеет,
а вот в проде было бы очень полезно.

Выглядит Lens примерно так:

<div align="center" style="margin-top:15px;">

![Lens example](/images/k3s_start/lens-example.png)

</div>

Для изучения **крайне настоятельно рекомендую** настроить Lens.

# Ваше первое приложение в кластере.

### Сервер

Давайте создадим своё первое приложение.

Для этого я буду использовать [express.js](https://expressjs.com/), так как он крайне популярен и прост.

Для этого сначала напишем сам сервер. Я буду использовать yarn, но можете и npm,
сути не поменяет.

Создайте какую-нибудь папку, где вы будете эксперементировать, откройте в ней ваш любимый тектовый редактор и просто копируйте файлы ниже.

```json{}[package.json]
{
  "name": "req_counter",
  "version": "1.0.0",
  // Указан модуль,
  // чтобы использовать нормальны импорты,
  // а не require.
  "type": "module",
  "license": "MIT",
  "scripts": {
    // Скрипт старта сервера.
    "server": "node index.js"
  },
  // Зависимости проекта.
  "dependencies": {
    "express": "^4.17.1"
  }
}
```

И само приложение

```js{}[index.js]
import express from "express";
import { hostname } from "os";
import { argv, exit } from "process";

// Серверное приложение.
const app = express();

// Глобальный счётчик входящих запросов.
let requests = 0;

// Обработка входящего запроса.
app.get("*", (req, res) => {
  // Увеличиваем глобальный счётчик запросов.
  requests += 1;
  // Логгируем входящий запрос.
  console.log(`${req.method} ${req.url}`);
  // Возвращаем информацию о текущем хосте и количестве запросов.
  res.json({
    requests: requests,
    hostname: hostname(),
  });
});

// Аргументы командной строки.
// Напрмиер yarn run server 127.0.0.1 8000
const args = argv.slice(2);

// Если передано неверное количество аргументов.
if (args.length != 2) {
  console.error("Usage: yarn run server {host} {port}");
  exit(1);
}

// Простейший "парсинг" аргументов командной строки.
const host = args[0];
const port = args[1];

// Старт сервера.
app.listen(port, host, () => {
  console.log(`Server listening at http://${host}:${port}`);
});
```

Это всё. Сервер готов.

<hr>
Протестируем запуск сервера выполнив команду ниже и открыв в
своём любимом браузере http://localhost:8080.

```
yarn run server 0.0.0.0 8080
```

У меня всё работает и успешно отдаётся нужная информация.

```json
{
  "requests": 1,
  "hostname": "s3rius-pc"
}
```

### Docker образ

Теперь создадим докер образ приложения.

Добавим `.dockerignore`, чтобы игнорировать ненужные файлы во время сборки образа.

```gitignore{}[.dockerignore]
node_modules/
```

И добавим в проект `Dockerfile` для описания самого процесса сборки.

```dockerfile{}[Dockerfile]
FROM node:17-alpine

WORKDIR /app
COPY . /app/

RUN yarn install

CMD [ "yarn", "run", "server", "0.0.0.0", "8000"]
```

Давайте соберём и запустим проект в контейнере.

```bash
docker build --tag="s3rius/req-counter-express:latest" .

docker run --rm -it -p 3400:8000 "s3rius/req-counter-express:latest"
```

Можете проверить, что приложение работает успешно, открыв в браузере http://localhost:3400.

У меня в ответ пришло то же сообщение. Только, как можно заметить,
`hostname` поменялся. На самом деле контейнеры используют свои hostname
отличные от `hostname` локальной машины.

```json
{
  "requests": 10,
  "hostname": "8f23adadc640"
}
```

Вариантов как положить это приложение в K8S несколько.

- Вы можете запушить собранное приложение в [Docker HUB](https://hub.docker.com/) и использовать его.
- Можете использовать мой образ `s3rius/req-counter-express:latest`
- Импортировать собранный образ как tar файл и импортировать его в containerd напрямую.
  Как это сделать почитать можно в [этой статье](https://cwienczek.com/2020/06/import-images-to-k3s-without-docker-registry/).

### Деплой в k8s

Создайте папку `kube` в папке проекта и теперь мы будем работать в ней.
Все ресурсы будут описаны yaml-файлами.

```yaml{}[kube/deployment.yml]
---
apiVersion: apps/v1
kind: Deployment
# метаданные самого деплоймента.
metadata:
  name: req-counter-deployment
spec:
  # Количество реплик пода.
  replicas: 1
  # Селектор, который выбирает
  # какие поды принадлежат этому деплойменту.
  selector:
    matchLabels:
      app: req-counter
  # Шаблон пода,
  # который будет использоваться при
  # маштабировании.
  template:
    # Метаданные пода.
    # тут обычно помещаются лейблы,
    # с помощью которых деплоймент идентифицирует
    # свои поды.
    metadata:
      labels:
        app: req-counter
    spec:
      # Масссив контейнеров
      containers:
        # Название контейнера внутри пода.
      - name: req-counter-app
        # Образ приложения.
        image: s3rius/req-counter-express:latest
        # Ресурсы требуемые для работы приложения.
        resources:
          # Минимальное количество ресурсов,
          # которое кластер гарантированно предоставит приложению.
          # Также данные значения используются для того,
          # чтобы выяснить на какой ноде запускать приложение.
          requests:
            memory: "50Mi"
            cpu: "30m"
          # Максимально возможные значения приложения.
          # Если приложение выйдет за лимиты,
          # то кубер убьёт приложение.
          limits:
            memory: "128Mi"
            cpu: "100m"
        # Порты на которых открыты приложения.
        ports:
        - containerPort: 8000
          protocol: TCP
```

Теперь опишем сервис для управления трафиком.

```yaml{}[kube/service.yml]
---
apiVersion: v1
kind: Service
# Метадата сервиса.
metadata:
  name: req-counter-service
spec:
  # Селектор подов,
  # которым будет пускаться трафик.
  # Трафик может идти в любой под,
  # который матчит данному селектору.
  selector:
    app: req-counter
  # Порты для проксирования соединений.
  ports:
    # Порт сервиса
  - port: 80
    # Порт пода, куда будет идти трафик дальше.
    targetPort: 8000
```

И в последнюю очередь опишем наш ингрес.

```yaml{}[kube/ingress.yml]
---
apiVersion: networking.k8s.io/v1
kind: Ingress
# Метаданные ингресса.
metadata:
  name: req-counter-ingress
  labels:
    name: req-counter-ingress
spec:
  # Правила роутинга.
  rules:
    # Требуемый хост.
  - host: req-counter.local
    http:
      paths:
        # Тип пути Prefix значит,
        # что все запросы, которые начинаются c
        # ${path} будут матчится тут.
      - pathType: Prefix
        # Сам путь.
        path: "/"
        backend:
          service:
            # Название нашего сервиса.
            name: req-counter-service
            # Порт сервиса, куда перенаправлять входящий трафик.
            port:
              number: 80
```

Перед тем, как создать все описанные ресурсы создадим неймспейс.

```bash
$ kubectl --context k3s create namespace req-couter-ns
namespace/req-couter-ns created
```

После того, как неймспейс создан самое время сделать последний штрих.

<div align="center">

![Apply meme](/images/k3s_start/kubectl-apply.png)

</div>

```bash
$ kubectl --context k3s --namespace req-couter-ns apply -f ./kube/
deployment.apps/req-counter-deployment created
ingress.networking.k8s.io/req-counter-ingress created
service/req-counter-service created
```

Готово. Теперь вы можете зайти в lens, выбрать свой кластер из списка
и посмотреть как там поживает ваше приложение.

Также не забудьте указать неймспей, в который вы беплоиди приложение.

Выглядит это чудо примерно так:

<div align="center">

![Deployed app in lens](/images/k3s_start/lens-deployed.png)

</div>

Также можно использовать команду и вывести всё себе в терминал.

```
$ kubectl --context k3s --namespace req-couter-ns get all
NAME                                          READY   STATUS    RESTARTS   AGE
pod/req-counter-deployment-764476db97-dt2tc   1/1     Running   0          8m11s

NAME                          TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
service/req-counter-service   ClusterIP   10.43.50.23   <none>        80/TCP    8m11s

NAME                                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/req-counter-deployment   1/1     1            1           8m11s

NAME                                                DESIRED   CURRENT   READY   AGE
replicaset.apps/req-counter-deployment-764476db97   1         1         1       8m11s
```

### Маштабирование

В рамках демонстрации давате поменяем значение
`replicas` в нашем файле деплоймента до `3`.

```yaml{}[kube/deployment.yml]
...
spec:
  # Количество реплик пода.
  replicas: 3
...
```

После изменения просто ещё раз вызовите команду apply.

```bash
$ kubectl --context k3s --namespace req-couter-ns apply -f ./kube/
deployment.apps/req-counter-deployment configured
ingress.networking.k8s.io/req-counter-ingress unchanged
service/req-counter-service unchanged
```

Как можно видеть изменился только наш `Deployment`.
Остальные ресурсы остались нетронутыми.

Давайте посмотрим на поды в нашем неймспейсе.

```
$ kubectl --context k3s --namespace req-couter-ns get pods
NAME                                      READY   STATUS    RESTARTS   AGE
req-counter-deployment-764476db97-dt2tc   1/1     Running   0          13m
req-counter-deployment-764476db97-tdjrb   1/1     Running   0          69s
req-counter-deployment-764476db97-x28fr   1/1     Running   0          69s
```

Как видно, всё правильно. Теперь у нас 3 пода
нашего приложения.


Теперь я пойду и выполню кучу запросов по адресу http://req-counter.local/
и получу балансировку между подами из коробки, без дополнительных 
конфигураций.

Если у вас не получается найти адрес. Добавьте данный хост себе в 
`/etc/hosts` на линуксе или в `C:\Windows\System32\drivers\etc\hosts` на windows,
дописав в конец файла следующее:

```
...
127.0.0.1 req-couter.local
```

И теперь вы можете открыть в браузере своё приложение и увидеть,
как кубернетес заботливо направляет трафик туда, куда вы хотите.

<div align="center">

![Making requests](/images/k3s_start/requests.gif)

</div>


### Как мне очистить мой кластер?

Всё очень просто. 

Так как у нас имеются описния ресурсов, то мы
можем удалить всё сразу используя команду

```bash
$ kubectl --context k3s --namespace req-couter-ns delete -f ./kube/
deployment.apps "req-counter-deployment" deleted
ingress.networking.k8s.io "req-counter-ingress" deleted
service "req-counter-service" deleted
```

Таким образом k8s удалит все описанные ресурсы из вашего кластера.

До новых встреч.
