Всем привет! Меня зовут Павел Агалецкий, я ведущий разработчик юнита Platform as a Service в Авито. В этой статье мы научимся запускать и отлаживать приложения в Kubernetes и познакомимся с двумя инструментами: утилитой kubectl и консольным дашбордом k9s.

Задача: запустить два приложения в Kubernetes
Мы попытаемся запустить в Kubernetes два приложения, которые будут взаимодействовать друг с другом через вызовы API.
Первое приложение — app1 — отвечает фразой Hello World  и текущим значением времени. Время app1 получает из второго приложения app2. Для этого оно подключается к app2 с помощью env-переменной APP2_URL — в ней должен быть указан адрес ко второму приложению.
func main() {
	app2URL, ok := os.LookupEnv("APP2_URL")
	if !ok {
		slog.Error("missing APP2_URL")
		os.Exit(1)
	}
	getTimeURL := fmt.Sprintf("%s/time", app2URL)
	http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
		slog.Info("Received request to /hello endpoint")
		app2Resp, err := http.DefaultClient.Get(getTimeURL)
		if erp != nil {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, "App2 (%) is not available: %v", getTimeURL, err)
			return
		}
		defer app2Resp.Body.Close()
		respTime, _ := io.ReadAll(app2Resp.Body)
		w.WriteHeader(http.StatusOK)
		fmt.Fprintf(w, "Hello World at %",, string(respTime))
	})
	slog.Info("Starting server on port 8890")
	err := http.ListenAndServe(":8890", nil)
	if err != nil {
		slog.Error("Application 1 finished with an error", "error", err)
	}
}Второе приложение — app2 — выводит текущее время в ответ на вызов своего API/time.
Вот код app2:
func main() {
	http.HandleFunc("/time", func(w http.ResponseWriter, r *http.Request) {
		slog.Info("Received request to /time endpoint")
		w.WriteHeader(http.StatusOK)
		fmt.Fprint(w, time.Now().String())
	})
	slog.Info("Starting server 2 on port 8891")
	err := http.ListenAndServe(":8891", nil)
	if err != nil {
		slog.Error("Application 2 finished with an error", "error", err)
	}
}Пробуем задеплоить контейнеры в кластер
Нам надо собрать контейнеры приложений. Для этого используем следующие Dockerfiles:
Для первого приложения:
FROM golang:1.21-alpine
COPY . /app
WORKDIR /app
RUN ls -la . && \
    go install . && \
    which kubeapp1
ENTRYPOINT ["/go/bin/kubeapp1"]Для второго приложения:
FROM golang:1.21-alpine
COPY . /app
WORKDIR /app
RUN ls -la . && \
    go install . && \
    which kubeapp2
ENTRYPOINT ["/go/bin/kubeapp2"]Теперь соберём их с помощью команды docker build:
# в директории app1:
$ docker build -t kubeapp1 .
# в директории app2:
$ docker build -t kubeapp2 .Подробнее вы можете прочитать в нашей предыдущей статье о работе с Kubernetes.
Убедимся, что они присутствуют в нашем docker-хосте, для этого воспользуемся командой docker images, она позволяет посмотреть существующие контейнеры в Docker.

Задеплоим контейнеры в кластер. Ниже показаны deployments.yaml для приложений, в которые намеренно внесены некоторые ошибки. Позже попробуем их устранить.
Для app1:
apiVersion: v1
kind: Namespace
metadata:
  name: app1
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubeapp1
  namespace: app1
  labels:
    app: kubeapp1
spec:
  selector:
    matchLabels:
      app: kubeapp1
  replicas: 10
  strategy:
    type: RollingUpdate
  template:
    metadata:
      namespace: app1
      labels:
        app: kubeapp1
    spec:
      containers:
        - name: app
          image: kubeapp1
          imagePullPolicy: Never
          ports:
            - containerPort: 8890Для app2:
apiVersion: v1
kind: Namespace
metadata:
  name: app2
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubeapp2
  namespace: app2
  labels:
    app: kubeapp2
spec:
  selector:
    matchLabels:
      app: kubeapp2
  replicas: 18
  strategy:
    type: RollingUpdate
  template:
    metadata:
      namespace: app2
      labels:
        app: kubeapp2
    spec:
      containers:
        - name: app
          image: kubeapp22
          imagePullPolicy: Never
          ports:
            - containerPort: 8891
- Теперь выполним деплой командой - kubectl apply:

- Проверим, работает ли первое приложение. Для этого посмотрим список подов с помощью команды - kubectl get pods -n app1:

Выясняем, почему поды висят в ошибке
Ошибка в app1
- Выясним причину ошибки с помощью команды - kubectl describe -n app1 pod/kubeapp1-5fcd8b8d9f-brwhh, где- kubeapp1-5fcd8b8d9f-brwhh— название пода.

Здесь видно, что контейнер запускается и тут же падает. На это указывает сообщение Back-off restarting failed container app in pod...
- Найдём причину падения в логах. Посмотреть их можно с помощью команды - kubectl logs -n app1 pod/kubeapp1-5fcd8b8d9f-brwhh

Как мы видим, ошибка в том, что мы не указали env-переменную APP2_URL.
Ошибка в app2
- Посмотрим на поды второго приложения через команду - kubectl get pods -n app2.Видим, что в app2 тоже плохо, но у его подов другой статус —- ErrImageNeverPull.

- Выясним причину такого статуса помощью команды - kubectl describe -n app1 pod/kubeapp1-5fcd8b8d9f-brwhh

Ошибка сообщает о том, что невозможно спуллить под kubeapp22. Так и есть: наш образ называется kubeapp2.  Ошибка сообщает, что образ с названием kubeapp22 отсутствует и не может быть скачен из registry. 
Это из-за ошибки в манифесте — вместо kubeapp22 надо написать kubeapp2.
Чиним проблему: исправляем ошибку в app2
В реальной ситуации стоило бы исправить описание ресурса в исходном файле, который мы использовали в Kubernetes, но в целях тренировки мы поправим ситуацию прямо в кластере куба.
Поды не являются самостоятельными единицами — они объединены в деплоймент, и именно в нём неверно указано название образа. Давайте это проверим c помощью команды kubectl get deployments -n app2

Так и есть. Деплоймент существует, но ни одна из опрошенных 18 реплик не работает. Чтобы увидеть полный манифест ресурса, добавим флаг -o yaml.

Как мы видим, в манифесте неправильно указано название образа. Отредактируем название с помощью команды kubectl edit -n app2 deployment/kubeapp2
Она откроет редактор по умолчанию. После этого нам надо будет поправить значение kubeapp22 на kubeapp2 и сохранить изменение. Такое изменение будет автоматически применено в кластере Kubernetes.
Теперь посмотрим на статусы подов еще раз:

Видим, что все поды теперь запустились и работают.
Чиним проблему: исправляем ошибку в app1
Давайте вернёмся к нашему первому приложению. Оно не работает, потому что мы не указали адрес второго приложения в env-переменной. Чтобы это исправить, нужно определить, какой будет адрес.
Пока адрес не доступен: мы не создали никакого специального ресурса, чтобы можно было получить доступ ко второму приложению. Нужный нам тип ресурса — так называемый сервис, или сокращённо svc.
Ресурс svc позволяет указать, что определённое приложение, запущенное в Kubernetes, должно быть доступно по таким-то адресам. Мы создавали такой тип ресурса в этом видео, чтобы получить доступ к нему снаружи кластера.
Давайте создадим ресурс сервис, чтобы приложение app1 могло получить доступ к приложению app1. Для этого используем команду kubectl expose -n app2 deployment/kubeapp2

Теперь сервис должен быть доступен. Выясним, по какому адресу, — с помощью команды kubectl get -n app2 svc 
Мы видим IP-адрес кластера, но ориентироваться на него не стоит — он может поменяться. Внутри нашего кластера работает специальный DNS. Это значит, что любой созданный сервис будет доступен по DNS-адресу.
Проверяем работу DNS в app2

Проверим, что мы можем использовать специальный DNS-адрес кластера. Для этого подключимся к пока единственно работающему в нашем кластере приложению app2. Посмотрим на список подов и подключимся к одному из них с помощью команды kubectl exec -n  app2 pod/kubeapp2-64486b645f-gqklp -it -- sh
Флаг -it – sh запустит внутри терминал и сделает его доступным в интерактивным режиме. 
Так как наш образ основан на Alpine, мы можем установить в него curl с помощью команды apk add curl.
Теперь попробуем обратиться к сервису по его имени kubeapp2. В консоль действительно выводится время — как и мы ожидаем от приложения app2.

Однако приложение app1 находится в другом неймспейсе и сможет получить доступ к app2 только по полному имени сервиса с указанием неймспейса. В нашем случае полное имя выглядит так: kubeapp2.app2.svc.cluster.local.
Полный адрес можно указать в переменную APP2_URL, отредактировав манифест. В этот раз давайте сделаем это через другой инструмент — утилиту k9s.
Устанавливаем k9s
k9s — это CLI-инструмент, который предоставляет удобный интерфейс для взаимодействия с кластерами Kubernetes и позволяет легко управлять ресурсами, подами, службами и другими объектами Kubernetes.
K9s обычно используется для управления и отладки приложений, развернутых на кластерах Kubernetes, а также для мониторинга состояния кластера в целом.
- Установим k9s: 
brew install k9s- Запустим k9s: 
k9sВнутри k9s — дашборд, на котором видны различные ресурсы кластера: поды, неймспейсы, сервисы, деплойменты.

Редактируем deployment в k9s
Наша задача — поменять значение env-переменной APP2_URL на полный DNS-адрес кластера.
- Перейдем в нужный неймспейс. 
- Нажмем клавишу - :.
- Введём в появившейся строке команду namespace. 

- С помощью Enter можно выбрать нужный неймспейс — нам интересен app1. Внутри мы увидим список подов приложения: 

- Чтобы посмотреть deployment приложения , нужно также нажать на - :и ввести deployments. У нас один деплоймент в этом неймспейсе —- kubeapp1.
- Отредактировать deployment можно с помощью клавиши - e. Добавим env-переменную- APP2_URL

После сохранения все внесённые изменения применятся в кластере Kubernetes, как и в предыдущем примере с kubeapp1.
Проверяем приложения после изменений
Теперь проверим состояние приложения. В k9s для этого достаточно нажать на название деплоймента.

Как мы видим, все образы находятся в состоянии Running — всё работает, приложение запущено.
Зайдём в один из подов и вызовем оттуда app2. Чтобы попасть в Shell, нужно нажать клавишу S на поде. 
Попробуем вызвать /time у app2:
curl http://kubeapp2.app2.svc.cluster.local:8891/time
Вызываем API app1 снаружи кластера
В разделе выше мы попробовали вызвать /time у app2 внутри кластера. А что будет, если сделать то же самое, но снаружи кластера? 
Для этого для начала выставим приложение app1 наружу. Не забудьте выйти из k9s с помощью Ctrl+D и Ctrl+C.
kubectl expose --type=NodePort -n app1 deployment/kubeapp1 
service/kubeapp1 exposed--type=NodePort указывает, что мы хотим получать доступ к сервису снаружи.
Попробуем сделать запрос на порт, который был присвоен нашему приложению снаружи. А сначала узнаем этот порт, для этого посмотрим список сервисов в неймспейсе app1.
kubectl get svc -n app1Как видно на скриншоте ниже, присвоенный порт — 30136. Давайте сделаем запрос на него. Узнать IP машины можно с помощью команды colima status.
curl 192.168.106:2:30136/hello
- Указываем, что мы хотим получать доступ к сервису снаружи. 
- Узнаём присвоенный приложению порт. 
- Узнаём IP машины. 
- Пробуем сделать запрос снаружи. 
Как вы видите, API успешно отрабатывает и при запросе снаружи.
Команды и горячие клавиши
В этой статье мы изучили несколько команд kubectl и познакомились с инструментом k9s, который позволяет работать с кластером в консоли в визуальном режиме. 
Команды kubectl
- kubectl apply -f <файл>— применить конфигурацию к кластеру из указанного файла или каталога. Команда создает или обновляет ресурсы в кластере.
- kubectl get pods— показать список всех подов в текущем пространстве имен. Можно добавить флаги для просмотра подов во всех пространствах имен или с определенными критериями.
- kubectl describe <ресурс> <имя>— показать подробную информацию о конкретном ресурсе, например о поде, включая состояние, переменные окружения, связанные ресурсы и другие важные сведения.
- kubectl logs <имя пода>— получить логи для конкретного пода. Это полезно для отладки и мониторинга работы приложений.
- kubectl get deployments— отобразить список всех деплойментов, которые управляют подами.
- kubectl edit <ресурс> <имя>— открыть редактор по умолчанию, позволяющий вносить изменения в спецификацию ресурса непосредственно из командной строки.
- kubectl expose— создать новый сервис (svc), который обеспечивает доступ к подам через сеть.
- kubectl get svc— показать список всех сервисов, доступных в кластере или неймспейсе .
- kubectl exec -it <имя поля> -- <команда>— выполнить команду внутри контейнера в поде. Опция -it позволяет интерактивно работать с контейнером через терминал. Это может быть полезно для отладки и управления системой изнутри контейнера.
Горячие клавиши в k9s
- :— ввести команду для выполнения.
- s— попасть в Shell на поде.
- e– отредактировать ресурс
- Ctrl+D, Ctrl+C — выйти из k9s. 
 
           
 
DEViant-OPtimiSt
Профдевормация это когда видишь o3 и думаешь - озон.)
chukov
Apache Ozone?