Atlantis
GitHub Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

3. 部署容器化应用

1. 前言

现在我们已经创建一些基础设施,可以开始部署应用了,这里约定:

  1. 需要手动编写 yaml 的应用,统一部署在 app 命名空间下
  2. 对于手动编写的 yaml,固定 label、matchLabels 和 selector 的写法,统一使用:app: 应用名,便于创建工作负载和暴露服务
  3. 使用 helm chart 部署的应用时,需要新建命名空间单独使用,避免异常时出现残留污染公共命名空间资源
  4. service 需要通过 ingressroute 暴露的,且只有一个 port 时,固定设置成 80(targetPort 按需配置),减少 ingressroute 里差异化配置

2. 常见应用

常见的应用有两种:无状态应用与有状态应用。

Kubernetes(K8s)中的 无状态应用 指的是应用程序的状态不依赖于任何单个实例的情况,换句话说,不需要挂载持久化存储,典型应用的可以是一个 web 服务器。

Kubernetes(K8s)中的 有状态应用 是指应用程序的状态对于其运行环境是重要的,换句话说,需要挂载持久化存储,典型应用可以是数据库。

我们分别使用 deployment 与 statefulset 来部署它们,可以用是否需要持久化存储来判断使用哪一种工作负载

2.1 无状态应用

以 Caddy 为例,我们创建一个监听 8080 端口的 web 服务器。

首先是创建配置文件,需要通过命令行操作,使用当前目录下的 Caddyfile 创建一个 configmap:

➜  kubectl -n app create configmap caddy --from-file=Caddyfile
configmap/gost created
➜  kubectl -n app get configmap caddy -o yaml
apiVersion: v1
data:
  Caddyfile: |
   :8080 {
   	# Set this path to your site's directory.
   	root * /usr/share/caddy

   	# Enable the static file server.
   	file_server
   	# Enable compression
   	encode zstd gzip
   	# Enable log
   	log {
   		output stdout
   	}
   }
kind: ConfigMap
metadata:
  creationTimestamp: "2024-03-30T08:55:13Z"
  name: caddy
  namespace: app
  resourceVersion: "182840788"
  uid: 49b3a6f8-c3c4-4a59-970a-24a2e392438f

然后创建一个 deployment,引用第一步中的 configmap,并配置一个 service 指向这个服务:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: caddy
  namespace: app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: caddy
  template:
    metadata:
      labels:
        app: caddy
    spec:
      containers:
        - name: caddy
          image: docker.io/caddy:2
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080
          volumeMounts:
            - name: config
              mountPath: /etc/caddy/Caddyfile
              subPath: Caddyfile
      volumes:
        - name: config
          configMap:
            name: caddy
---
apiVersion: v1
kind: Service
metadata:
  name: caddy
  namespace: app
spec:
  type: ClusterIP
  selector:
    app: caddy
  ports:
    - name: http
      port: 80
      targetPort: 8080

最后在公共 ingressroute 的 routes 中添加一个 rule 暴露 service,这里按照 Reverse DNS 的方式配置域名,格式为:service.namespace.basedomain

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: ingressroute
  namespace: kube-system
spec:
  entryPoints:
  - websecure
  routes:
  ...
  - kind: Rule
    match: Host(`caddy.app.wbuntu.com`)
    services:
    - name: caddy
      namespace: app
      port: 80
  ...
  tls:
    secretName: tls-cert

然后配置DNS解析(或者修改本地 hosts)访问域名,可以看到 Caddy 的欢迎页面:

2.2 有状态应用

在第二章中,我们已经利用 statefulset 部署过数据库与缓存,这里再重提一次。

K3s 默认携带了 local-path-provisioner 组件,并设置为默认 storageclass:

➜  ~ kubectl get sc
NAME                   PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-path (default)   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  2y91d

利用 statefulset 的 volumeClaimTemplates,可以实现自动创建 pvc 并挂载到 Pod 的,如此一来就不需要手动管理 ,简化创建有状态应用的步骤,一个典型的有状态应用,如 redis,如下:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
  namespace: storage
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
        - name: redis
          image: docker.io/library/redis:6
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 6379
          volumeMounts:
          - name: data
            mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 5Gi
---
apiVersion: v1
kind: Service
metadata:
  name: redis
  namespace: storage
spec:
  type: ClusterIP
  selector:
    app: redis
  ports:
    - name: redis
      port: 6379
      targetPort: 6379

我们定义了一个存储卷模板 data,并将它挂载到容器的 /data,启动 pod 后会看到自动创建了一个与 redis 的 statefulset 关联的 pvc。

➜  ~ kubectl -n storage get pvc data-redis-0
NAME           STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
data-redis-0   Bound    pvc-1a2665f2-79c0-4b97-a8d6-cbd3e219492e   5Gi        RWO            local-path     470d
删除 statefulset 不会一并删除它关联的 pvc,只有手动删除 pvc,触发 pv 的删除才会真正清除数据。

3. 其他应用

除了 deployment 和 statefulset 外,K8s 还定义了以下常用工作负载:

  1. daemonset:守护进程集,确保在每个节点上都运行一个副本,通常用于在集群中的每个节点上运行特定的系统服务或 Daemon 进程
  2. job:任务,用于一次性任务或批处理任务,并确保任务完成后不会重新启动
  3. cronjob:定时任务,定期执行 job 的工作负载,类似于 cron 定时任务,可以按照预定的时间间隔或时间表来触发 job

这些不做过多展开,在用到时会说明。