3. 部署容器化应用
现在我们已经创建一些基础设施,可以开始部署应用了,这里约定:
- 需要手动编写 yaml 的应用,统一部署在 app 命名空间下
- 对于手动编写的 yaml,固定 label、matchLabels 和 selector 的写法,统一使用:
app: 应用名
,便于创建工作负载和暴露服务 - 使用 helm chart 部署的应用时,需要新建命名空间单独使用,避免异常时出现残留污染公共命名空间资源
- service 需要通过 ingressroute 暴露的,且只有一个 port 时,固定设置成 80(targetPort 按需配置),减少 ingressroute 里差异化配置
常见的应用有两种:无状态应用与有状态应用。
Kubernetes(K8s)中的 无状态应用 指的是应用程序的状态不依赖于任何单个实例的情况,换句话说,不需要挂载持久化存储,典型应用的可以是一个 web 服务器。
Kubernetes(K8s)中的 有状态应用 是指应用程序的状态对于其运行环境是重要的,换句话说,需要挂载持久化存储,典型应用可以是数据库。
我们分别使用 deployment 与 statefulset 来部署它们,可以用是否需要持久化存储来判断使用哪一种工作负载
以 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 的欢迎页面:
在第二章中,我们已经利用 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 的删除才会真正清除数据。
除了 deployment 和 statefulset 外,K8s 还定义了以下常用工作负载:
- daemonset:守护进程集,确保在每个节点上都运行一个副本,通常用于在集群中的每个节点上运行特定的系统服务或 Daemon 进程
- job:任务,用于一次性任务或批处理任务,并确保任务完成后不会重新启动
- cronjob:定时任务,定期执行 job 的工作负载,类似于 cron 定时任务,可以按照预定的时间间隔或时间表来触发 job
这些不做过多展开,在用到时会说明。