GitOps

[TOC]

GitOps 的核心思想是,通过 Git 以声明式的方式来定义环境和基础设施。

01 构建容器镜像

前提:安装 Docker Desktop

运行容器镜像

拉取镜像:

docker pull lyzhang1999/hello-world-flask:latest

查看镜像:

docker images
REPOSITORY                      TAG       IMAGE ID       CREATED        SIZE
lyzhang1999/hello-world-flask   latest    185eac234bc3   7 months ago   163MB

运行镜像:

docker run -d -p 8000:5000 lyzhang1999/hello-world-flask:latest 
curl http://localhost:8000

进入容器内部

查看运行中的容器:

docker ps
CONTAINER ID   IMAGE                                  COMMAND                  CREATED         STATUS         PORTS                    NAMES
7bcc2a8c1a8e   lyzhang1999/hello-world-flask:latest   "python3 -m flask ru…"   4 minutes ago   Up 4 minutes   0.0.0.0:8000->5000/tcp   blissful_chandrasekhar

进入容器内部::

docker exec -it 7bcc2a8c1a8e bash

停止容器:

docker stop 7bcc2a8c1a8e
docker rm -f 7bcc2a8c1a8e

构建容器镜像

app.py

from flask import Flask
import os
app = Flask(__name__)
app.run(debug=True)

@app.route('/')
def hello_world():
    return 'Hello, my first docker images! ' + os.getenv("HOSTNAME") + ''

requirements.txt

Flask==2.2.2

Dockerfile

# syntax=docker/dockerfile:1

FROM python:3.8-slim-buster

RUN apt-get update && apt-get install -y procps vim apache2-utils && rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt

COPY . .

CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0"]

构建镜像:

docker build -t hello-world-flask .

查看镜像:

docker images
REPOSITORY                      TAG       IMAGE ID       CREATED         SIZE
hello-world-flask               latest    0ef4626bd411   5 seconds ago   163MB

运行镜像:

docker run -d -p 8000:5000 hello-world-flask
curl http://localhost:8000
Hello, my first docker images! 4f07773e527e

推送镜像

docker login
docker tag hello-world-flask liaozibo/hello-world-flask
docker push liaozibo/hello-world-flask

常用基础镜像

02 K8s 和 Kind

  • 安装 kubectl
  • 安装 kind (全称 Kubernetes in Docker,单机测试 K8s 集群最佳方案)

创建 K8s 集群

config.yml(Manifest 清单文件)

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  kubeadmConfigPatches:
  - |
    kind: InitConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"
  extraPortMappings:
  - containerPort: 80
    hostPort: 80
    protocol: TCP
  - containerPort: 443
    hostPort: 443
    protocol: TCP
kind create clusters --config config.yml

部署容器镜像到 K8s 集群

创建 Pod 工作负载

hello-flask.yml

  • kind: 工作负载类型(在实际项目中,通常不会直接创建 Pod 类型的工作负载)
  • metadata.name: 工作负载名称
  • containers: 容器配置
apiVersion: v1
kind: Pod
metadata:
  name: hello-world-flask
spec:
  containers:
    - name: flask
      image: liaozibo/hello-world-flask:latest
      ports:
        - containerPort: 5000
kubectl apply -f hello-flask.yml

查看正在运行的 Pod:

kubectl get pods -w
NAME                READY   STATUS    RESTARTS   AGE
hello-world-flask   1/1     Running   0          10m

访问 Pod(端口转发,本地网络到集群网络):

kubectl port-forward pod/hello-world-flask 8000:5000
curl http://localhost:8000
Hello, my first docker images! hello-world-flask

进入容器:

kubectl exec -it hello-world-flask -- bash

删除 Pod:

kubectl delete pod hello-world-flask

03 K8s 自愈和自动扩容

自愈:

  • 节点尝试故障时,自动重启并恢复服务
  • 自动故障转移(保证流量不会被转发到不健康的节点)

创建 Deployment 工作负载

创建 Deployment 工作负载(管理 Pod)

kubectl create deployment hello-world-flask --image liaozibo/hello-world-flask:latest --replicas=2

输出 Manifest

kubectl create deployment hello-world-flask --image liaozibo/hello-world-flask:latest --replicas=2 --dry-run=client -o yaml

创建 Service(相当于负载均衡器,将流量以加权负载均衡的方式转发到 Pod)

kubectl create service clusterip hello-world-flask --tcp=5000:5000

创建 Ingress(相当于集群的外网访问入口)

kubectl create ingress hello-world-flask --rule="/=hello-world-flask:5000"

部署 Ingress-Nginx

kubectl create -f https://cdn.jsdelivr.net/gh/liaozibo-dev/resource@main/ingress-nginx/ingress-nginx.yaml

访问 Deployment 工作负载

Kind 本地集群(暴露 80/443 端口):

kind get clusters
kind

Pod:

kubectl get pods
NAME                                 READY   STATUS    RESTARTS   AGE
hello-world-flask-68bfc7dc45-49zcv   1/1     Running   0          31m
hello-world-flask-68bfc7dc45-s5l6t   1/1     Running   0          31m

Service:

kubectl get service
NAME                TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
hello-world-flask   ClusterIP   10.96.134.76   <none>        5000/TCP   29m
kubernetes          ClusterIP   10.96.0.1      <none>        443/TCP    16h

Ingress:

kubectl get ingress
NAME                CLASS    HOSTS   ADDRESS     PORTS   AGE
hello-world-flask   <none>   *       localhost   80      27m

访问集群:

curl http://localhost
Hello, my first docker images! hello-world-flask-68bfc7dc45-s5l6t
Hello, my first docker images! hello-world-flask-68bfc7dc45-49zcv
kind(80:80) -> ingress(80:hello-world-flask-5000) -> service(5000:5000) -> Pod(5000)

自动自愈

K8s 会自动移除故障 Pod,重启服务后重新加入

kubectl exec -it hello-world-flask-68bfc7dc45-49zcv -- bash -c "killall python3"

自动扩容

创建 K8s Metric Server(提供监控指标)

kubectl apply -f https://cdn.jsdelivr.net/gh/liaozibo-dev/resource@main/metrics/metrics.yaml

等待 Metric 工作负载就绪

kubectl wait deployment -n kube-system metrics-server --for condition=Available=True --timeout=90s
deployment.apps/metrics-server condition met

创建自动扩容策略:

  • CPU 阈值:50%
  • 最小副本数:2
  • 最大副本数:10
kubectl autoscale deployment hello-world-flask --cpu-percent=50 --min=2 --max=10

更新 Deployment

Linux:

kubectl patch deployment hello-world-flask --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/resources", "value": {"requests": {"memory": "100Mi", "cpu": "100m"}}}]'
deployment.apps/hello-world-flask patched

Windows:

kubectl patch deployment hello-world-flask --type="json" -p="[{\"op\": \"add\", \"path\": \"/spec/template/spec/containers/0/resources\", \"value\": {\"requests\": {\"memory\": \"100Mi\", \"cpu\": \"100m\"}}}]"
deployment.apps/hello-world-flask patched

模拟并发:

  • c: 50 并发
  • n: 10000 次请求
kubectl exec -it hello-world-flask-6558d95457-4bcvf -- bash
ab -c 50 -n 10000 http://127.0.0.1:5000/

自动扩容和缩容:

NAME                                 READY   STATUS              RESTARTS   AGE
hello-world-flask-6558d95457-4bcvf   1/1     Running             0          2m54s
hello-world-flask-6558d95457-blqxm   0/1     ContainerCreating   0          1s
hello-world-flask-6558d95457-frfds   1/1     Running             0          3m7s
hello-world-flask-6558d95457-jzrd6   0/1     Pending             0          1s

K8s 应用发布和回滚之 GitOps

手动发布

手动发布的三种方式:

  • 更新镜像:kubectl set image
  • 更新 Manifest:kubectl apply -f
  • 编辑 Manifest:kubectl edit deployment

更新镜像

查看之前部署的 Deployment:

kubectl get deployment
NAME                READY   UP-TO-DATE   AVAILABLE   AGE
hello-world-flask   2/2     2            2           3h49m

更新镜像版本:

kubectl set image deployment/hello-world-flask hello-world-flask=lyzhang1999/hello-world-flask:v1
kubectl get pods
NAME                                 READY   STATUS              RESTARTS   AGE
hello-world-flask-5467fbd748-68777   0/1     ContainerCreating   0          14s
hello-world-flask-6558d95457-7rck9   1/1     Running             0          136m
hello-world-flask-6558d95457-gm9gv   1/1     Running             0          136m

更新 Manifest

kubectl apply -f hello-world-flask.yml

编辑 Manifest

kubectl edit deployment hello-world-flask

GitOps 发布工作流

GitOps:以 Git 版本控制为理念的 DevOps 实践。

将 Manifest 存储在 Git 仓库中,一旦修改并提交 Manifest,GitOps 工作流会自动对比差异并进行部署。

搭建 FluxCD

安装 FluxCD (CD 持续部署,FluxCD 用于监听 Git 仓库变化)(实际生产中推荐使用 ArgoCD)

kubectl apply -f https://cdn.jsdelivr.net/gh/liaozibo-dev/resource@main/fluxcd/fluxcd.yaml

等待安装完成

kubectl wait --for=condition=available --timeout=300s --all deployments -n flux-system
deployment.apps/helm-controller condition met
deployment.apps/image-automation-controller condition met
deployment.apps/image-reflector-controller condition met
deployment.apps/kustomize-controller condition met
deployment.apps/notification-controller condition met
deployment.apps/source-controller condition met

deployment.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: hello-world-flask
  name: hello-world-flask
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello-world-flask
  template:
    metadata:
      labels:
        app: hello-world-flask
    spec:
      containers:
      - image: liaozibo/hello-world-flask:latest
        name: hello-world-flask

将文件提交到 GitHub (public)仓库中

git init
git add .
git commit -m "add deployment"
git branch -M main
git remote add origin https://github.com/liaozibo-dev/fluxcd-demo.git
git push -u origin main

为 FluxCD 创建仓库连接信息 fluxcd-repo.yml

apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: GitRepository
metadata:
  name: hello-world-flask
spec:
  interval: 5s
  ref:
    branch: main
  url: https://github.com/liaozibo-dev/fluxcd-demo.git

将其部署到集群中:

kubectl apply -f fluxcd-repo.yml

检查配置状态:

kubectl get gitrepository
NAME                URL                                               AGE   READY   STATUS
hello-world-flask   https://github.com/liaozibo-dev/fluxcd-demo.git   18s   True    stored artifact for revision 'main/c04e6492afce39f4e891bbe97b40187a354b3b1a'

创建部署策略:fluxcd-kustomize.yml

apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
  name: hello-world-flask
spec:
  interval: 5s
  path: ./
  prune: true
  sourceRef:
    kind: GitRepository
    name: hello-world-flask
  targetNamespace: default
kubectl apply -f fluxcd-kustomize.yml
kubectl get kustomization 
NAME                AGE   READY   STATUS
hello-world-flask   22s   True    Applied revision: main/c04e6492afce39f4e891bbe97b40187a354b3b1a

自动发布

更新 deployment.yml 镜像版本(lyzhang1999/hello-world-flask:v1

提交 deployment.yml

git add deployment.yml
git commit -m "update deployment" 
git push

验证自动发布:

kubectl describe kustomization hello-world-flask
kubectl get kustomization
curl http://localhost 
Hello, my v1 version docker images! hello-world-flask-67d6474f8f-xsncs

自动回滚

回滚 deployment.yml

git log
commit f641e06dc00acfb2beac5f04bf52eb52711fae78 (HEAD -> main, origin/main)
Author: liaozibo <liaozibo@qq.com>
Date:   Tue May 2 20:36:13 2023 +0800

    update deployment

commit c04e6492afce39f4e891bbe97b40187a354b3b1a
Author: liaozibo <liaozibo@qq.com>
Date:   Tue May 2 20:27:43 2023 +0800

    add deployment
git reset --hard c04e6492afce39f4e891bbe97b40187a354b3b1a
git push -f
curl http://localhost
Hello, my first docker images! hello-world-flask-68bfc7dc45-j8p6q

参考

  • 《云原生架构与 GitOps 实战》 入门篇:从零上手GitOps