使用 Redis 部署 PHP 留言板应用程序

2,065 阅读13分钟

github.com/huayin-open…

集群检查

在部署应用前有必要对集群环境进行检查,确认集群环境是否正常、集群的版本信息,以及集群中的节点是否正常运行。

查看上下文

> kubectl config get-contexts                  
CURRENT   NAME               CLUSTER            AUTHINFO                               NAMESPACE
          docker-desktop     docker-desktop     docker-desktop                         
*         minikube           minikube           minikube                               default

切换上下文

> kubectl config use-context minikube
CURRENT   NAME               CLUSTER            AUTHINFO                               NAMESPACE
          docker-desktop     docker-desktop     docker-desktop                         
*         minikube           minikube           minikube                               default

查看 k8s 版本信息

> kubectl version
Client Version: version.Info{Major:"1", Minor:"23", GitVersion:"v1.23.1", GitCommit:"86ec240af8cbd1b60bcc4c03c20da9b98005b92e", GitTreeState:"clean", BuildDate:"2021-12-16T11:33:37Z", GoVersion:"go1.17.5", Compiler:"gc", Platform:"darwin/arm64"}
Server Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.4", GitCommit:"b695d79d4f967c403a96986f1750a35eb75e75f1", GitTreeState:"clean", BuildDate:"2021-11-17T15:42:41Z", GoVersion:"go1.16.10", Compiler:"gc", Platform:"linux/arm64"}

查看集群状态

如果集群不在运行状态,可以检查一下 kubelet 的状态,因为 kube-apiserver 是由 kubelet 管理的。

> kubectl cluster-info
Kubernetes control plane is running at https://kubernetes.docker.internal:6443
CoreDNS is running at https://kubernetes.docker.internal:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

查看集群节点,只有单个节点

> kubectl get no -o wide
NAME             STATUS   ROLES                  AGE     VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE         KERNEL-VERSION     CONTAINER-RUNTIME
docker-desktop   Ready    control-plane,master   3d21h   v1.22.4   192.168.65.4   <none>        Docker Desktop   5.10.76-linuxkit   docker://20.10.11

查看 node 说明

> kubectl explain no
KIND:     Node
VERSION:  v1

DESCRIPTION:
     Node is a worker node in Kubernetes. Each node will have a unique
     identifier in the cache (i.e. in etcd).

FIELDS:
   apiVersion   <string>
     APIVersion defines the versioned schema of this representation of an
     object. Servers should convert recognized schemas to the latest internal
     value, and may reject unrecognized values. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources

   kind <string>
     Kind is a string value representing the REST resource this object
     represents. Servers may infer this from the endpoint the client submits
     requests to. Cannot be updated. In CamelCase. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

   metadata     <Object>
     Standard object's metadata. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata

   spec <Object>
     Spec defines the behavior of a node.
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

   status       <Object>
     Most recently observed status of the node. Populated by the system.
     Read-only. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

部署后端 Redis

创建主 Redis Deployment [create]

> kubectl create -f redis-leader-deployment.yaml
deployment.apps/redis-leader created

查看 Deployment 状态

> kubectl get deployments -l app=redis -l role=leader
NAME           READY   UP-TO-DATE   AVAILABLE   AGE
redis-leader   1/1     1            1           63m

查看 Pod 状态 [yaml]

> kubectl get po -l app=redis -l role=leader -o yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2022-01-02T12:43:25Z"
  generateName: redis-leader-5d66d78fcb-
  labels:
    app: redis
    pod-template-hash: 5d66d78fcb
    role: leader
    tier: backend
  name: redis-leader-5d66d78fcb-l2drh
  namespace: default
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: redis-leader-5d66d78fcb
    uid: 942d6581-6ab6-4f5a-b849-1f80ab2e7e21
  resourceVersion: "161467"
  uid: a5fc9aa2-9534-43af-b09b-7240457cda1a
spec:
  containers:
  - image: registry.cn-shenzhen.aliyuncs.com/kubeops/redis:6.0.5
    imagePullPolicy: IfNotPresent
    name: leader
    ports:
    - containerPort: 6379
      protocol: TCP
    resources:
      requests:
        cpu: 100m
        memory: 100Mi
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-kjzqs
      readOnly: true
  dnsPolicy: ClusterFirst
  enableServiceLinks: true
  nodeName: docker-desktop
  preemptionPolicy: PreemptLowerPriority
  priority: 0
  restartPolicy: Always
  schedulerName: default-scheduler
  securityContext: {}
  serviceAccount: default
  serviceAccountName: default
  terminationGracePeriodSeconds: 30
  tolerations:
  - effect: NoExecute
    key: node.kubernetes.io/not-ready
    operator: Exists
    tolerationSeconds: 300
  - effect: NoExecute
    key: node.kubernetes.io/unreachable
    operator: Exists
    tolerationSeconds: 300
  volumes:
  - name: kube-api-access-kjzqs
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
            path: namespace
status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: "2022-01-02T12:43:25Z"
    status: "True"
    type: Initialized
  - lastProbeTime: null
    lastTransitionTime: "2022-01-02T12:43:26Z"
    status: "True"
    type: Ready
  - lastProbeTime: null
    lastTransitionTime: "2022-01-02T12:43:26Z"
    status: "True"
    type: ContainersReady
  - lastProbeTime: null
    lastTransitionTime: "2022-01-02T12:43:25Z"
    status: "True"
    type: PodScheduled
  containerStatuses:
  - containerID: docker://30b948eff36f3bbc4489e4b338e7e95f1150a4c5281e4d33031d9f28027f2819
    image: redis:6.0.5
    imageID: docker-pullable://redis@sha256:800f2587bf3376cb01e6307afe599ddce9439deafbd4fb8562829da96085c9c5
    lastState: {}
    name: leader
    ready: true
    restartCount: 0
    started: true
    state:
      running:
        startedAt: "2022-01-02T12:43:25Z"
  hostIP: 192.168.65.4
  phase: Running
  podIP: 10.1.0.51
  podIPs:
  - ip: 10.1.0.51
  qosClass: Burstable
  startTime: "2022-01-02T12:43:25Z"

排查 Pod 启动失败问题

如果 Pod 的 STATUS 长时间没有变为 Running,可以通过 kubectl describe pod <POD_NAME> 查看 Pod 的详细信息。

> kubectl describe po redis-leader-fb76b4755-nw4x4
Name:         redis-leader-fb76b4755-nw4x4
Namespace:    default
Priority:     0
Node:         docker-desktop/192.168.65.4
Start Time:   Sun, 02 Jan 2022 20:10:26 +0800
Labels:       app=redis
              pod-template-hash=fb76b4755
              role=leader
              tier=backend
Annotations:  <none>
Status:       Running
IP:           10.1.0.46
IPs:
  IP:           10.1.0.46
Controlled By:  ReplicaSet/redis-leader-fb76b4755
Containers:
  leader:
    Container ID:   docker://8df17907fec2b38966ec46c9d71bfc89fe77519952df327449d54150c4e1384b
    Image:          docker.io/redis:6.0.5
    Image ID:       docker-pullable://redis@sha256:800f2587bf3376cb01e6307afe599ddce9439deafbd4fb8562829da96085c9c5
    Port:           6379/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Sun, 02 Jan 2022 20:10:27 +0800
    Ready:          True
    Restart Count:  0
    Requests:
      cpu:        100m
      memory:     100Mi
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-ggts8 (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             True 
  ContainersReady   True 
  PodScheduled      True 
Volumes:
  kube-api-access-ggts8:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   Burstable
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  4m9s  default-scheduler  Successfully assigned default/redis-leader-fb76b4755-nw4x4 to docker-desktop
  Normal  Pulled     4m8s  kubelet            Container image "docker.io/redis:6.0.5" already present on machine
  Normal  Created    4m8s  kubelet            Created container leader
  Normal  Started    4m8s  kubelet            Started container leader

创建主 Redis service

> kubectl create -f redis-leader-service.yaml
service/redis-leader created

查看主 Redis service 状态 [wide]

> kubectl get svc -l app=redis -l role=leader -o wide
NAME           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE   SELECTOR
redis-leader   ClusterIP   10.105.105.38   <none>        6379/TCP   23m   app=redis,role=leader,tier=backend

创建两个从 Redis 副本

> kubectl create -f redis-follower-deployment.yaml
deployment.apps/redis-follower created

验证两个从 Redis 副本在运行

> kubectl get po -l app=redis -l role=follower
NAME                              READY   STATUS    RESTARTS   AGE
redis-follower-74cc7db576-fstd2   1/1     Running   0          106s
redis-follower-74cc7db576-kwbsc   1/1     Running   0          106s

创建从 Redis service

> kubectl create -f redis-follower-service.yaml
service/redis-follower created

查看从 Redis service 状态 [json]

> kubectl get svc -l app=redis -l role=follower -o json
{
    "apiVersion": "v1",
    "items": [
        {
            "apiVersion": "v1",
            "kind": "Service",
            "metadata": {
                "creationTimestamp": "2022-01-02T12:46:02Z",
                "labels": {
                    "app": "redis",
                    "role": "follower",
                    "tier": "backend"
                },
                "name": "redis-follower",
                "namespace": "default",
                "resourceVersion": "161635",
                "uid": "92c2298a-cc31-4c3b-b1fb-bdbf25f80975"
            },
            "spec": {
                "clusterIP": "10.105.1.89",
                "clusterIPs": [
                    "10.105.1.89"
                ],
                "internalTrafficPolicy": "Cluster",
                "ipFamilies": [
                    "IPv4"
                ],
                "ipFamilyPolicy": "SingleStack",
                "ports": [
                    {
                        "port": 6379,
                        "protocol": "TCP",
                        "targetPort": 6379
                    }
                ],
                "selector": {
                    "app": "redis",
                    "role": "follower",
                    "tier": "backend"
                },
                "sessionAffinity": "None",
                "type": "ClusterIP"
            },
            "status": {
                "loadBalancer": {}
            }
        }
    ],
    "kind": "List",
    "metadata": {
        "resourceVersion": "",
        "selfLink": ""
    }
}

部署留言板前端

创建前端 Deployment [apply]

> kubectl apply -f frontend-deployment.yaml

验证三个前端副本正在运行

> kubectl get po -l app=guestbook -l tier=frontend -o wide
NAME                        READY   STATUS    RESTARTS   AGE     IP          NODE             NOMINATED NODE   READINESS GATES
frontend-57756596cb-m2jj7   1/1     Running   0          6m51s   10.1.0.54   docker-desktop   <none>           <none>
frontend-57756596cb-skz9h   1/1     Running   0          6m51s   10.1.0.53   docker-desktop   <none>           <none>
frontend-57756596cb-xqc6n   1/1     Running   0          6m51s   10.1.0.55   docker-desktop   <none>           <none>

创建前端服务

> kubectl apply -f frontend-service.yaml
service/frontend created

验证前端服务正在运行

> kubectl get svc -l tier=frontend
NAME       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
frontend   ClusterIP   10.105.140.183   <none>        80/TCP    18m

通过 kubectl port-forward 查看前端服务

运行以下命令将本机的 8080 端口转发到服务的 80 端口。

> kubectl port-forward svc/frontend 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

扩展 Web 前端

> kubectl scale deployment frontend --replicas=5

验证正在运行的前端 Pod 的数量

> kubectl get po -l app=guestbook -l tier=frontend
NAME                        READY   STATUS    RESTARTS   AGE
frontend-57756596cb-g76s5   1/1     Running   0          46s
frontend-57756596cb-h8xhm   1/1     Running   0          46s
frontend-57756596cb-m2jj7   1/1     Running   0          38m
frontend-57756596cb-skz9h   1/1     Running   0          38m
frontend-57756596cb-xqc6n   1/1     Running   0          38m

查看 Redis Pod 日志

> kubectl logs redis-leader-5d66d78fcb-pvd84
1:C 02 Jan 2022 12:44:53.370 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 02 Jan 2022 12:44:53.370 # Redis version=6.0.5, bits=64, commit=00000000, modified=0, pid=1, just started
1:C 02 Jan 2022 12:44:53.370 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
1:M 02 Jan 2022 12:44:53.370 * Running mode=standalone, port=6379.
1:M 02 Jan 2022 12:44:53.370 # Server initialized
1:M 02 Jan 2022 12:44:53.370 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
1:M 02 Jan 2022 12:44:53.371 * Ready to accept connections
1:M 02 Jan 2022 12:44:53.975 * Replica 10.1.0.50:6379 asks for synchronization
1:M 02 Jan 2022 12:44:53.975 * Partial resynchronization not accepted: Replication ID mismatch (Replica asked for '5f3f86fcc80e66d778048b0d0876f094154ebb65', my replication IDs are 'ef5856ce660b088d5e8f1a75a50d07d624d18326' and '0000000000000000000000000000000000000000')
1:M 02 Jan 2022 12:44:53.975 * Replication backlog created, my new replication IDs are '6e7879f5f854ed376103ea1eb10c5579d21f2d64' and '0000000000000000000000000000000000000000'
1:M 02 Jan 2022 12:44:53.975 * Starting BGSAVE for SYNC with target: disk
1:M 02 Jan 2022 12:44:53.975 * Background saving started by pid 21
1:M 02 Jan 2022 12:44:53.976 * Replica 10.1.0.49:6379 asks for synchronization
1:M 02 Jan 2022 12:44:53.977 * Partial resynchronization not accepted: Replication ID mismatch (Replica asked for '5f3f86fcc80e66d778048b0d0876f094154ebb65', my replication IDs are '6e7879f5f854ed376103ea1eb10c5579d21f2d64' and '0000000000000000000000000000000000000000')
1:M 02 Jan 2022 12:44:53.977 * Waiting for end of BGSAVE for SYNC
21:C 02 Jan 2022 12:44:53.978 * DB saved on disk
21:C 02 Jan 2022 12:44:53.979 * RDB: 0 MB of memory used by copy-on-write
1:M 02 Jan 2022 12:44:54.073 * Background saving terminated with success
1:M 02 Jan 2022 12:44:54.073 * Synchronization with replica 10.1.0.50:6379 succeeded
1:M 02 Jan 2022 12:44:54.073 * Synchronization with replica 10.1.0.49:6379 succeeded

进入 Redis 容器查看数据

> kubectl exec -it redis-leader-5d66d78fcb-pvd84 -- redis-cli
127.0.0.1:6379> KEYS *
1) "guestbook"
127.0.0.1:6379> GET guestbook
",test,hello"

后续维护

给所有 Redis Pod 添加 status 标签

> kubectl label pods -l app=redis status=healthy
pod/redis-follower-74cc7db576-fstd2 labeled
pod/redis-follower-74cc7db576-kwbsc labeled
pod/redis-leader-5d66d78fcb-pvd84 labeled

查看 Redis Pod 的标签 [--show-labels]

> kubectl get po -l app=redis --show-labels                    
NAME                              READY   STATUS    RESTARTS   AGE   LABELS
redis-follower-74cc7db576-fstd2   1/1     Running   0          96m   app=redis,pod-template-hash=74cc7db576,role=follower,status=healthy,tier=backend
redis-follower-74cc7db576-kwbsc   1/1     Running   0          96m   app=redis,pod-template-hash=74cc7db576,role=follower,status=healthy,tier=backend
redis-leader-5d66d78fcb-pvd84     1/1     Running   0          85m   app=redis,pod-template-hash=5d66d78fcb,role=leader,status=healthy,tier=backend

更新主 Redis Pod 的标签

> kubectl label pods -l app=redis -l role=leader status=unhealthy --overwrite
pod/redis-leader-5d66d78fcb-pvd84

查看主 Redis 资源使用情况

> kubectl top po -l app=redis -l role=leader
NAME                            CPU(cores)   MEMORY(bytes)   
redis-leader-5d66d78fcb-9bm92   2m           3Mi

使用 edit 更新从 Redis Pod 的副本数量

> kubectl edit deploy/redis-follower
deployment.apps/redis-follower edited

# 查看 Pod 副本数量,从 2 变成 3
> kubectl get pods -l app=redis -l role=follower
NAME                              READY   STATUS    RESTARTS   AGE
redis-follower-74cc7db576-cvf2q   1/1     Running   0          9m29s
redis-follower-74cc7db576-mz6h8   1/1     Running   0          25s
redis-follower-74cc7db576-twq9x   1/1     Running   0          9m29s

解决镜像拉取问题

由于 guestbook 前端镜像是在谷歌的 gcr.io 上,所以如果没有代理是无拉取的,这里选择在香港服务器上将镜像同步到阿里云容器镜像服务上。

拉取镜像

> docker pull gcr.io/google_samples/gb-frontend:v5
v5: Pulling from google_samples/gb-frontend
72a69066d2fe: Pull complete 
fbf13c1e88c3: Pull complete 
cddf91161400: Pull complete 
2c396aa97b98: Pull complete 
1d9707294ce1: Pull complete 
443be0efd1a3: Pull complete 
f40e54f5a6bb: Pull complete 
449e25c19260: Pull complete 
4116245e7948: Pull complete 
063a257bdaed: Pull complete 
ba6b06b0aa4f: Pull complete 
331cb0169fcf: Pull complete 
7889266700ad: Pull complete 
2648f8d3ecd7: Pull complete 
2b19c0592f6e: Pull complete 
c3b640245fb3: Pull complete 
4a8a6bc16a1b: Pull complete 
Digest: sha256:1ffc7816e028b2e2f2b592594383a0139b9f570ff5fcc5fdfd81806aa8d403bf
Status: Downloaded newer image for gcr.io/google_samples/gb-frontend:v5
gcr.io/google_samples/gb-frontend:v5

重命名镜像

> docker tag gcr.io/google_samples/gb-frontend:v5

登录阿里云容器镜像服务

> docker login registry.cn-shenzhen.aliyuncs.com -ukubeops
Password: 
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

推送镜像到阿里云容器镜像服务

> docker push registry.cn-shenzhen.aliyuncs.com/kubeops/gb-frontend:v5
The push refers to repository [registry.cn-shenzhen.aliyuncs.com/kubeops/gb-frontend]
6fd1212aa9ea: Pushed 
76f17e2309c9: Pushed 
4b6e88f9653e: Pushed 
2aecaf70d382: Pushed 
80cd58a1bab0: Pushed 
2e559a423c71: Pushed 
ede4b550d621: Pushed 
2dba6c06bdde: Pushed 
ecd6a695b6ea: Pushed 
b01bf213b941: Pushed 
0ccbc08ded6d: Pushed 
449cff66aba3: Pushed 
643f1d079de7: Pushed 
0104a3ee0257: Pushed 
fd3ee495df7c: Pushed 
00f117848faf: Pushed 
ad6b69b54919: Pushed 
v5: digest: sha256:1ffc7816e028b2e2f2b592594383a0139b9f570ff5fcc5fdfd81806aa8d403bf size: 3876

更新镜像

> kubectl set image deploy/redis-follower follower=registry.cn-shenzhen.aliyuncs.com/kubeops/gb-redis-follower:v2
deployment.apps/redis-follower image updated

使用 Kustomize 部署

添加 kustomization.yaml 配置文件

resources:
  - redis-leader-service.yaml
  - redis-leader-deployment.yaml
  - redis-follower-service.yaml
  - redis-follower-deployment.yaml
  - frontend-service.yaml
  - frontend-deployment.yaml

删除旧的部署

之前的所有资源这个时候也可以一次性删除。

> kubectl delete -k .
service "frontend" deleted
service "redis-follower" deleted
service "redis-leader" deleted
deployment.apps "frontend" deleted
deployment.apps "redis-follower" deleted
deployment.apps "redis-leader" deleted

一键部署

> kubectl apply -k .
service/frontend created
service/redis-follower created
service/redis-leader created
deployment.apps/frontend created
deployment.apps/redis-follower created
deployment.apps/redis-leader created

回滚

回滚是很常见的操作,这里我们使用 kubectl rollout undo 来实现。

查看 Deployment 版本

> kubectl rollout history deploy redis-follower
deployment.apps/redis-follower
REVISION  CHANGE-CAUSE
1         <none>
2         <none>
3         <none>

回滚到上一个版本

> kubectl rollout undo deploy redis-follower

回滚到指定版本

> kubectl rollout undo deploy redis-follower --to-revision=1

Pod 水平自动伸缩

相较 kubectl scale,这个能力更加强大,他会根据当前资源的使用情况,自动进行伸缩。

创建 Horizontal Pod Autoscaler

--cpu-percent=80 指的是 HPA 将(通过 Deployment)增加或者减少 Pod 副本的数量以保持所有 Pod 的平均 CPU 利用率在 80% 左右。

> kubectl autoscale deploy frontend --min=5 --max=10 --cpu-percent=80
horizontalpodautoscaler.autoscaling/frontend autoscaled

查看 Autoscaler 的状态

> kubectl get hpa
NAME       REFERENCE             TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
frontend   Deployment/frontend   1%/80%    5         10        5          3m32s