Kubernetes. Deployment. Разворачиваем приложение из заранее созданного Docker-образа

Материал из Wiki
Перейти к навигации Перейти к поиску

Описание

Deployment - деплой в кластер

Это такая расширенная версия ReplicaSet.

Переходим к следующей абстракции Kubernetes - Deployment. Они управляют наборами replicaset для непрерывного обновления подов. Покажу на примере, о чем идет речь. Допустим, у вас вышло обновление приложения в новом контейнере. Вам нужно не останавливая сервис выкатить обновление в прод. Если вы измените версию контейнера в шаблоне replicaset, автоматически он у вас не обновится. Да, если pod со старой версией контейнера упадет, новый будет создан уже с новой версией. Но все работающие поды останутся на старой версии.

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

Настройка

Пример 1. Nginx

Деплоймент, который создаст 2 реплики пода nginx02

nano ./deployment-nginx.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx02
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx02
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: nginx02
    spec:
      containers:
      - image: nginx:1.16
        name: nginx02
        ports:
        - containerPort: 80

kubectl apply -f deployment-nginx.yaml

kubectl get deployment -o wide

    ...
    NAME      READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS   IMAGES       SELECTOR
    nginx02   2/2     2            2           10m     nginx02      nginx:1.16   app=nginx02
    ...

kubectl get pod -o wide

    ...
    NAME                       READY   STATUS    RESTARTS   AGE     IP               NODE         NOMINATED NODE   READINESS GATES
    nginx02-59dc87447c-9nrkj   1/1     Running   0          10m     172.16.213.141   kub-node-1   <none>           <none>
    nginx02-59dc87447c-d7vf5   1/1     Running   0          10m     172.16.213.77    kub-node-2   <none>           <none>    
    ...    

До создания сервиса можно временно пробросить порт в деплоймент и проверить что nginx из контейнера отвечает по порту 80 на ноде, на которой запустили временный проброс

kubectl port-forward --address 0.0.0.0 deployments/nginx02 80:80

На любой Linux машине:

curl "http://192.168.199.184/"

Если nginx отвечает - нажимаем Ctrl+C в консоли где запускали kubectl port-forward --address 0.0.0.0 deployments/nginx02 80:80

Тепрь создадим service чтобы перманентно пробросить порты в deployment nginx02

nano ./service.yaml

apiVersion: v1
kind: Service
metadata:
  name: nginx02
spec:
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: nginx02
  type: NodePort

kubectl apply -f service.yaml

kubectl get svc -o wide

    ...
    NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE     SELECTOR
    kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP        2d12h   <none>
    nginx02      NodePort    10.101.196.165   <none>        80:32032/TCP   6m41s   app=nginx02
    ...

Nginx Pod теперь развернут в кластере Kubernetes и доступен через Интернет

Доступ из веб-браузера по порту 32032 (МОЖНО ПРОЙТИ ПО IP ЛЮБОЙ НОДЫ КЛАСТЕРА KUBERNETES)

http://192.168.199.184:32032/

http://192.168.199.186:32032/

http://192.168.199.180:32032/

Изменить количество реплик deployment

kubectl scale deployment nginx02 --replicas=3

Удалить deployment

kubectl delete deployment nginx02

Пример 2. Go App

Создание кастомного Docker-образа контейнера "goapp"

Приложение на GO будет работать на порте 8080 и отдавать результат работы через HTTP

mkdir ./goapp

cd ./goapp

nano ./main.go

package main
import (
   "fmt"
   "log"
   "net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
   fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
}
func main() {
   http.HandleFunc("/", handler)
   log.Fatal(http.ListenAndServe(":8080", nil))
}

Dockerfile для создания образа контейнера с приложением

Each instruction creates one layer:

       FROM creates a layer from the golang:1.10 Docker image.

       COPY adds files from your Docker client’s current directory.

       RUN builds your application with make.

       CMD specifies what command to run within the container.

nano Dockerfile

FROM golang:1.10
WORKDIR /app
COPY . .
RUN go build -o main .
CMD ["/app/main"]

Собираем ораз

docker build . -t goapp:v0.1

docker images

А теперь поправим Докерфайл на такой и соберем с тэгом v0.2

nano Dockerfile

# Создаем временный Докер-образ и называем его builder
FROM golang:1.10 AS builder
WORKDIR /app
COPY . .
# Компилируем приложение специально для дальнейшего запуска в Alpine
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

# Создаем финальный Докер-образ на базе легковесного alpine
FROM alpine:latest
WORKDIR /root/
# Копируем собранное приложение из образа biulder
COPY --from=builder ./app .
EXPOSE 8080
CMD ["./app"]

docker build . -t goapp:v0.2

docker images

    ...
    REPOSITORY   TAG             IMAGE ID       CREATED          SIZE
    goapp           v0.2            c3b7c4cb4a1f   30 minutes ago   12.1MB
    <none>       <none>          698ac8b17e93   30 minutes ago   782MB
    goapp           v0.1            c73b00641856   42 minutes ago   767MB
    ...

Второй Докерфайл дал нам образ, который в 50 раз меньше весит. Как это вышло?

В первом Докерфайле мы сказали Докеру собрать образ, в котором будет Golang версии 1.10, а также наше приложение. Но Go - это компилируемый язык, его артефакт - исполняемый бинарный файл, который не обязательно запускать, имея рядышком достаточно тяжеловесный компилятор Golang, он нам нужен лишь при сборке приложения.

Поэтому во втором Докерфайле мы использовали Golang и собрали бинарь в первом этапе, а во втором этапе мы использовали легковесный Alpine, чтобы запускать наше приложение

А еще мы только что сделали первый веб-сервис в Докере:

docker run -d -p 8080:8080 goapp:v0.2

docker ps -a

    ...
    CONTAINER ID   IMAGE         COMMAND               CREATED          STATUS                      PORTS                                       NAMES
    ee2eacdb1a6f   goapp:v0.2       "./app"               29 minutes ago   Up 15 minutes               0.0.0.0:8080->8080/tcp, :::8080->8080/tcp   reverent_mclean
    ...

netstat -luptn

    ...
    tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      7505/docker-proxy
    ...

Проверяем

curl "http://127.0.0.1:8080/gogogo"

    ...
    Hi there, I love gogogo!
    ...

Заливаем образ "goapp" на hub.docker.com

Для начала идем на hub.docker.com и создаем аккаунт для хранения образов

cless - логин аккаунта на hub.docker.com

docker login

docker tag goapp:v0.2 cless/goapp:v0.2

docker push cless/goapp:v0.2

Теперь на hub.docker.com у нас есть образ приложения goapp:v0.2

Деплоим приложение "goapp" в Kubernetes

Создаем namespace goapp-ns

kubectl create namespace goapp-ns

Деплоймент, который скачает образ cless/goapp:v0.2 из hub.docker.com и создаст 2 реплики пода goapp в namespace goapp-ns

cd ./goapp

nano ./deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: goapp
  namespace: goapp-ns
spec:
  replicas: 2
  selector:
    matchLabels:
      app: goapp
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: goapp
    spec:
      containers:
      - image: cless/goapp:v0.2
        name: goapp
        ports:
        - containerPort: 8080

kubectl apply -f deployment.yaml

kubectl -n goapp-ns get deployment -o wide

    ...
    NAME    READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES             SELECTOR
    goapp   2/2     2            2           12s   goapp        cless/goapp:v0.2   app=goapp
    ...

kubectl -n goapp-ns get pod -o wide

    ...
    NAME                    READY   STATUS    RESTARTS   AGE   IP               NODE         NOMINATED NODE   READINESS GATES
    goapp-89574b689-jpnm4   1/1     Running   0          59s   172.16.213.80    kub-node-2   <none>           <none>
    goapp-89574b689-v6wqr   1/1     Running   0          59s   172.16.213.146   kub-node-1   <none>           <none>
    ...    

До создания сервиса можно временно пробросить порт в деплоймент и проверить что nginx из контейнера отвечает по порту 80 на ноде, на которой запустили временный проброс

kubectl -n goapp-ns port-forward --address 0.0.0.0 deployments/goapp 8080:8080

На любой Linux машине:

curl "http://192.168.199.184:8080/"

Если nginx отвечает - нажимаем Ctrl+C в консоли где запускали kubectl -n goapp-ns port-forward --address 0.0.0.0 deployments/goapp 8080:8080

Тепрь создадим service чтобы перманентно пробросить порты в deployment goapp

nano ./service.yaml

apiVersion: v1
kind: Service
metadata:
  name: goapp
  namespace: goapp-ns
spec:
  ports:
  - port: 8080
    targetPort: 8080
    nodePort: 30050
  selector:
    app: goapp
  type: NodePort

kubectl apply -f service.yaml

kubectl -n goapp-ns get svc -o wide

    ...
    NAME    TYPE       CLUSTER-IP    EXTERNAL-IP   PORT(S)          AGE   SELECTOR
    goapp   NodePort   10.97.72.66   <none>        8080:30050/TCP   8s    app=goapp
    ...

Goapp Pod теперь развернут в кластере Kubernetes и доступен через Интернет

Доступ из веб-браузера по порту 30050 (МОЖНО ПРОЙТИ ПО IP ЛЮБОЙ НОДЫ КЛАСТЕРА KUBERNETES)

http://192.168.199.184:30050/

http://192.168.199.186:30050/

http://192.168.199.180:30050/

Изменить количество реплик deployment

kubectl -n goapp-ns scale deployment goapp --replicas=3

Удалить сервис

kubectl -n goapp-ns delete svc goapp

Удалить deployment

kubectl -n goapp-ns delete deployment goapp