云原生 01 K8s 极简实战

[TOC]

部署示例应用

示例应用

代码仓库:https://github.com/liaozibo-dev/kubernetes-example.git

应用业务架构:

  • 前端:frontend
  • 后端:backend
  • K8s Manifest:deploy

K8s 部署架构:

            -- http://host/     --> frontend-service  -> frontend deployment
Ingress -
            -- http://host/api  --> backend-service   -> backend deployment(HPA) --> postgrep-service   -> postgrep deployment

创建 K8s 集群

删除之前的 kind 集群

kind delete cluster

kind 集群 config.yml

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 cluster --config config.yml

部署 Ingress

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

部署 Metric,以便开启 HPA

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

部署示例应用

创建命名空间 example

kubectl create namespace example

创建 Postgres 数据库

kubectl apply -n example -f https://cdn.jsdelivr.net/gh/liaozibo-dev/kubernetes-example@main/deploy/database.yaml

创建前后端 Deployment 工作负载和 Service:

kubectl apply -n example -f https://cdn.jsdelivr.net/gh/liaozibo-dev/kubernetes-example@main/deploy/frontend.yaml
kubectl apply -n example -f https://cdn.jsdelivr.net/gh/liaozibo-dev/kubernetes-example@main/deploy/backend.yaml

创建 Ingress

kubectl apply -n example -f https://cdn.jsdelivr.net/gh/liaozibo-dev/kubernetes-example@main/deploy/ingress.yaml

创建 HPA

kubectl apply -n example -f https://cdn.jsdelivr.net/gh/liaozibo-dev/kubernetes-example@main/deploy/hpa.yaml

等待应用部署完成

kubectl get pods -n example
NAME                        READY   STATUS              RESTARTS   AGE
backend-5c4c868bc6-qxp5c    0/1     ContainerCreating   0          9m33s
frontend-6b48fbbc48-zzlh4   0/1     ContainerCreating   0          10m
postgres-7568bd77cf-pnnc9   0/1     ImagePullBackOff    0          12m
kubectl wait --for=condition=Ready pods --all -n example --timeout=300s

创建的资源总览

kubectl get all -n example
NAME                            READY   STATUS    RESTARTS      AGE
pod/backend-5c4c868bc6-7qmvx    1/1     Running   0             33m
pod/backend-5c4c868bc6-ksspg    1/1     Running   0             33m
pod/frontend-6b48fbbc48-vdxv6   1/1     Running   2 (11m ago)   33m
pod/frontend-6b48fbbc48-vh7st   1/1     Running   0             33m
pod/postgres-7568bd77cf-47wkm   1/1     Running   0             33m

NAME                       TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/backend-service    ClusterIP   10.96.129.218   <none>        5000/TCP   33m
service/frontend-service   ClusterIP   10.96.30.56     <none>        3000/TCP   33m
service/pg-service         ClusterIP   10.96.96.184    <none>        5432/TCP   33m

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/backend    2/2     2            2           33m
deployment.apps/frontend   2/2     2            2           33m
deployment.apps/postgres   1/1     1            1           33m

NAME                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/backend-5c4c868bc6    2         2         2       33m
replicaset.apps/frontend-6b48fbbc48   2         2         2       33m
replicaset.apps/postgres-7568bd77cf   1         1         1       33m

NAME                                           REFERENCE             TARGETS           MINPODS   MAXPODS   REPLICAS   AGE
horizontalpodautoscaler.autoscaling/backend    Deployment/backend    29%/50%, 1%/50%   2         10        2          33m
horizontalpodautoscaler.autoscaling/frontend   Deployment/frontend   0%/80%            2         2         2          33m

一键部署所有资源

-f deploy 指定了部署 deploy 目录下的所有 Manifest

git clone https://github.com/liaozibo-dev/kubernetes-example.git
cd kubernetes-example
kubectl apply -f deploy -n example

命名空间

使用命名空间隔离团队及应用环境。

系统级命名空间:

  • default:默认命名空间
  • kube-public:所有用户都可以读取的命名空间
  • kube-system:K8s 系统级组件的命名空间
  • kube-node-lease:集群扩展相关的命名空间

查看命名空间

查看命名空间:

kubectl get ns
NAME                 STATUS   AGE
default              Active   46m
example              Active   43m
ingress-nginx        Active   43m
kube-node-lease      Active   46m
kube-public          Active   46m
kube-system          Active   46m
local-path-storage   Active   46m

查看命名空间详情:

kubectl describe namespace example
Name:         example
Labels:       kubernetes.io/metadata.name=example
Annotations:  <none>
Status:       Active

No resource quota.

No LimitRange resource.

创建命名空间

通过命令行创建:

kubectl create namespace example

通过 Manifest 创建:(namespace.yml)

apiVersion: v1
kind: Namespace
metadata:
  name: example
kubectl apply -f namespace.yml

删除命名空间

删除命名空间会删除该命名空间下的所有资源

kubectl delete namespace example

指定命名空间

在 Manifest 中指定:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  namespace: example   # 设置 namespace
  labels:
    app: frontend
spec:

在命令行中指定:(更常用)

kubectl apply -f deploy -n example

查看命名空间下的资源

kubectl get deployment -n example
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
backend    2/2     2            2           63m
frontend   2/2     2            2           63m
postgres   1/1     1            1           64m

命名空间使用

命名空间作用:

  • 环境管理:隔离不同环境(开发、测试、生产)。命名空间提供了一种软隔离的方式,实际中,建议使用不同集群对环境进行硬隔离。
  • 隔离:隔离不同团队、产品线等
  • 资源控制:可以在命名空间级别配置 CPU、内存等资源配额,避免资源恶心竞争导致业务不稳定的情况
  • 权限控制:K8s 的 RBAC 可以对某个用户授权一个或多个命名空间
  • 提高集群性能:提高资源搜索的性能

跨命名空间通信:

  • 同一命名空间:可以直接通过 service-name 通信
  • 不同命名空间:需要通过完整的 Service URL 进行通信 <service-name>.<namespace-name>.svc.cluster.local

命名空间规划:

  • 小型组织:在同一集群下,通过命名空间隔离不同环境
  • 大型组织:通过不同集群隔离环境,并且通过命名空间隔离不同团队(如果不同团队开发的是同一业务的不同微服务,可以将这些微服务在放在同一命名空间下)

工作负载

K8s 的工作负载包括:ReplicaSet、Deployment、StatefulSet、DaemonSet、Job、CronJob

ReplicaSet

ReplicaSet:保持一定数量的 Pod 始终处于运行状态

关系图:

                - Pod
ReplicaSet -
                - Pod

创建 ReplicaSet 工作负载:ReplicaSet.yml


apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    app: frontend
spec:
  replicas: 3   # 3 个副本数
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: lyzhang1999/frontend:v1
kubectl apply -f ReplicaSet.yml

查看镜像版本:

kubectl get pods --selector=app=frontend -o jsonpath='{.items[*].spec.containers[0].image}'

ReplicaSet 只负载维护 Pod 数量。 更新镜像版本并提交到 K8s 中,Pod 的镜像版本不会自动更新。只有删除 Pod,ReplicaSet 才会用新的镜像版本创建 Pod。

Deployment

通常,无状态的业务应用都会使用 Deployment 部署。

Deployment 是 K8s 中最常用的工作负载

  • 可以实现更新(不停机的滚动更新)、回滚、横向扩容
  • 配置 HPA 可以实现自动扩缩容

关系图:

                - ReplicaSet - Pods
Deployment - 
                - ReplicaSet - Pods

查看示例应用中 backend 的工作负载详情:

kubectl describe deployment backend -n example
Name:                   backend
Namespace:              example
CreationTimestamp:      Wed, 03 May 2023 15:01:51 +0800
Labels:                 app=backend
Annotations:            deployment.kubernetes.io/revision: 1
Selector:               app=backend
Replicas:               10 desired | 10 updated | 10 total | 2 available | 8 unavailable
StrategyType:           RollingUpdate # 部署策略
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge # 最大不可用数量、最大超出期望的 Pod 数量
Pod Template:
  Labels:  app=backend
  Containers:
   flask-backend:
    Image:      lyzhang1999/backend:latest
    Port:       5000/TCP
    Host Port:  0/TCP
    Limits:
      cpu:     256m
      memory:  256Mi
    Requests:
      cpu:      128m
      memory:   128Mi
    Liveness:   http-get http://:5000/healthy delay=0s timeout=1s period=10s #success=1 #failure=5
    Readiness:  http-get http://:5000/healthy delay=10s timeout=1s period=10s #success=1 #failure=5
    Startup:    http-get http://:5000/healthy delay=10s timeout=1s period=10s #success=1 #failure=5
    Environment:
      DATABASE_URI:       pg-service
      DATABASE_USERNAME:  postgres
      DATABASE_PASSWORD:  postgres
    Mounts:               <none>
  Volumes:                <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Progressing    True    NewReplicaSetAvailable
  Available      False   MinimumReplicasUnavailable
OldReplicaSets:  <none>
NewReplicaSet:   backend-5c4c868bc6 (10/10 replicas created) # 由 Deployment 管理的 ReplicaSet 名称
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  32s   deployment-controller  Scaled up replica set backend-5c4c868bc6 to 4 from 2
  Normal  ScalingReplicaSet  17s   deployment-controller  Scaled up replica set backend-5c4c868bc6 to 8 from 4
  Normal  ScalingReplicaSet  1s    deployment-controller  Scaled up replica set backend-5c4c868bc6 to 10 from 8

监控 ReplicaSet:

kubectl get replicaset --watch -n example
NAME                  DESIRED   CURRENT   READY   AGE
backend-5c4c868bc6    10        10        10      5h6m
frontend-6b48fbbc48   2         2         2       5h6m
postgres-7568bd77cf   1         1         1       5h6m

将最小 Pod 数量由 2 改成 3:

kubectl patch hpa backend -p "{\"spec\":{\"minReplicas\": 3}}" -n example

更新镜像版本,Deployment 会滚动更新 Pod

kubectl set image deployment/backend flask-backend=lyzhang1999/backend:v1 -n example

StatefulSet

  • StatefulSet 主要用于部署 “有状态” 的应用。
  • 在实际的业务场景里,StatefulSet 经常用来部署中间件。
  • StatefulSet 可以很好的支持中间件主从关系操作,以及可以配合持久化存储一起使用。

实际中,通常不需要自己写 StatefulSet Manifest,只要找到对于中间件的 Helm Chart 直接安装即可。

或者可以直接使用云厂商提供的高可用产品。

DaemonSet

节点级的守护进程,集群中添加新节点时,它会在进行节点启动新的 Pod。它可以用于日志和监控组件,采集节点的日志或监控指标。

Job/CronJob

相对于其他工作负载,如果 Pod 结束了,K8s 会不断重启 Pod。 而 Job/CronJob 主要用于执行一次性的批处理任务。

服务发现

K8s 原生的服务发现机制:Service

通过 IP 通信

获取后端服务的 IP 地址

kubectl get pods -n example --selector=app=backend -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP            NODE                 NOMINATED NODE   READINESS GATES
backend-5b9b8f78-4xt5k   1/1     Running   0          68m   10.244.0.38   kind-control-plane   <none>           <none>
backend-5b9b8f78-dxmj2   1/1     Running   0          69m   10.244.0.35   kind-control-plane   <none>           <none>
backend-5b9b8f78-mdw5k   1/1     Running   0          69m   10.244.0.33   kind-control-plane   <none>           <none>

进入前端服务的 Pod 并调用后端服务:

kubectl exec -n example -it frontend-6b48fbbc48-2xxdr -- sh
kubectl exec -it $(kubectl get pods --selector=app=frontend -n example -o jsonpath="{.items[0].metadata.name}") -n example -- sh
/frontend # wget -O - http://10.244.0.38:5000/healthy
Connecting to 10.244.0.38:5000 (10.244.0.38:5000)
writing to stdout
{"healthy":true}

Service 服务发现

/frontend # wget -O - http://backend-service:5000/healthy
Connecting to backend-service:5000 (10.96.129.218:5000)
writing to stdout
{"healthy":true}
while true; do wget -q -O- http://backend-service:5000/host_name && sleep 1; done

Service 原理:

Service -> Endpoint(保存 pod ip 集合) -> Pods

backend Service Manifest:

apiVersion: v1
kind: Service
metadata:
  name: backend-service # Service 域名
  labels:
    app: backend
spec:
  type: ClusterIP # Service 类型
  sessionAffinity: None # 保持会话
  selector:
    app: backend # 通过 Label 关联 Pod
  ports:
  - port: 5000 # Service 监听的端口
    targetPort: 5000 # 目标端口

Service 完整域名:{$service_name}.{$namespace}.svc.cluster.local 或者 {$service_name}.{$namespace}

创建 Service 后,K8s 会自动创建 Endpoint:

kubectl get endpoints -n example
NAME               ENDPOINTS                                            AGE
backend-service    10.244.0.33:5000,10.244.0.35:5000,10.244.0.38:5000   6h43m
frontend-service   10.244.0.15:3000,10.244.0.17:3000                    6h43m
pg-service         10.244.0.18:5432                                     6h43m

Service 类型

Service 类型:

  • ClusterIP:(最常用)为 Service 创建一个 VIP,并提供集群内的访问能力
  • NodePort:(不推荐)将 Service 暴露在节点的端口上,可以通过 节点IP+端口 的方式访问服务
  • Loadbalancer:(不推荐)将 Service 和云厂商的负载均衡器关联起来,通过负载均衡器的外网 IP 进行访问
  • ExternalName:将 Service 和另一个域名关联起来

ExternalName:在 default 命名空间下请求 http://backend-serivce:5000,会被转发到 example 命名空间下


apiVersion: v1
kind: Service
metadata:
  name: backend-service
  namespace: default
spec:
  type: ExternalName
  externalName: backend-service.example.svc.cluster.local

通过 Service 访问外部服务

  • Service 和 Endpoint 通过 metadata.name 关联
  • Service 类型是 ClusterIP
  • K8s 集群内通过 mysql-service.example.svc.cluster.local:3306 访问 ```yaml apiVersion: v1 kind: Service metadata: name: mysql-service namespace: example spec: ports:
    • port: 3306 targetPort: 3306 — apiVersion: v1 kind: Endpoints metadata: name: mysql-service namespace: example subsets:
    • addresses:
      • ip: 8.8.8.8 ports:
      • port: 3306 protocol: TCP ```

应用配置

不推荐直接将配置文件 COPY 到镜像中:

  • 一般镜像表示的业务进程,只有源码改变时才会重新构建镜像
  • 生效效率低,不支持热更新
  • 安全性,敏感配置不应让开发直接接触

Env

为 Deployment 配置环境变量

backend.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
  ......
spec:
  replicas: 1
  ......
    spec:
      containers:
      - name: flask-backend
        image: lyzhang1999/backend:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 5000
        env:
        - name: DATABASE_URI
          value: pg-service
        - name: DATABASE_USERNAME
          value: postgres
        - name: DATABASE_PASSWORD
          value: postgres

ConfigMap

ConfigMap:能够在 Pod 启动时,将 ConfigMap 的内容以文件的方式挂载到容器里

kubectl get configmap pg-init-script -n example -o yaml
apiVersion: v1
data:
  CreateDB.sql: |-
    CREATE TABLE text (
        id serial PRIMARY KEY,
        text VARCHAR ( 100 ) UNIQUE NOT NULL
    );
kind: ConfigMap
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"CreateDB.sql":"CREATE TABLE text (\n    id serial PRIMARY KEY,\n    text VARCHAR ( 100 ) UNIQUE NOT NULL\n);"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"pg-init-script","namespace":"example"}}
  creationTimestamp: "2023-05-03T07:01:42Z"
  name: pg-init-script
  namespace: example
  resourceVersion: "956"
  uid: 27451ea4-4c28-45e3-977b-c986fe71dfc6

在 deployment 中引用 ConfigMap:


apiVersion: apps/v1
kind: Deployment
metadata:
  name: Postgres
  ......
spec:
  ......
  template:
    ......
    spec:
      containers:
        - name: Postgres
          image: Postgres
          volumeMounts:
            - name: sqlscript # 卷名称
              mountPath: /docker-entrypoint-initdb.d # 挂载路径
          ......
      volumes:
        - name: sqlscript # 卷名称
          configMap: # 以卷的方式使用 ConfigMap
            name: pg-init-script

其他:

  • 环境变量引用 ConfigMap(envFrom
  • 从文件创建 ConfigMap(kubectl create configmap --from-file

    Secret

Secret:将配置以加密文件的形式挂载到容器(Base64加密)

创建 Secret:


apiVersion: v1
kind: Secret
metadata:
  name: pg-init-script
  namespace: example
type: Opaque
data:
  CreateDB.sql: |-
    Q1JFQVRFIFRBQkxFIHRleHQgKAogICAgaWQgc2VyaWFsIFBSSU1BUlkgS0VZLAogICAgdGV4dCBWQVJDSEFSICggMTAwICkgVU5JUVVFIE5PVCBOVUxMCik7

在 deployment 引用 secret 后,会实时挂载到容器中,并且文件内容是 Base64 解码后的

$ kubectl edit deployment postgres -n example
......
volumes:
  - secret:
      secretName: pg-init-script
    name: sqlscript
......

参考

  • 《云原生架构与 GitOps 实战》 核心基础篇:K8s极简实战