跳到主要内容

部署

2024年08月29日
柏拉文
越努力,越幸运

一、认识


Kubernetes(简称 K8s 是由 Google 开源的、用于容器化应用的自动化部署、扩展和管理的平台。它被设计用于管理大量容器化服务,通过集群的方式协调和编排容器资源,提供高可用性和可扩展性。Kubernetes 是现代应用部署和管理的首选方案。

1.1 Kubernetes 工作机制

Kubernetes 的工作机制涉及到其多个核心组件和资源

一、Kubernetes 架构: Kubernetes 的架构是由多个组件组成的,这些组件协调合作,提供容器的部署、管理和运维功能。

1.1 集群架构:

  • Master 节点(控制平面):控制集群的状态并提供 API 服务。Master 节点主要管理以下组件:

    • API Server:是 Kubernetes 的控制入口,负责接收和处理 API 请求,集群所有的操作都通过 API Server 与其他组件进行交互。

    • Controller Manager:负责执行集群的控制循环(如 Pod 的副本控制、节点管理等),确保集群的目标状态与实际状态一致。

    • Scheduler:负责将 Pod 调度到合适的节点上,依据资源需求、节点的状态等条件来选择最优节点。

    • etcdKubernetes 的分布式键值存储,存储集群的所有配置和状态信息,是集群的核心数据存储。

  • Node 节点:是集群中运行容器的工作机器,每个节点都有以下组件:

    • Kubelet:负责维护节点上 Pod 的生命周期,确保容器运行在节点上,并且与 API Server 保持同步。

    • Kube Proxy:负责实现集群内的网络负载均衡,帮助 Pod 之间进行通信。

    • Container Runtime:负责实际运行容器,常见的容器运行时有 DockerContainerd 等。

1.2 资源对象: Kubernetes 中的所有工作都基于资源对象进行管理,常见的资源对象包括:

  • Pod:是 Kubernetes 的最小部署单元,一个 Pod 可以包含一个或多个容器。Kubernetes 通过 Pod 来管理容器的生命周期。

  • Service:为一组 Pod 提供统一的网络访问入口,通过负载均衡和服务发现将流量路由到合适的 Pod。

  • Deployment:控制 Pod 的副本数和版本,支持滚动更新和回滚。

  • ConfigMapSecret:分别用于存储非敏感配置和敏感信息,如数据库密码。

  • Namespace:提供了一个逻辑上的隔离,可以在一个集群中创建多个 Namespace,避免资源冲突。

二、Kubernetes 的工作机制: Kubernetes 的工作机制依赖于声明式的配置和控制循环,核心理念是通过 控制器(Controller 确保集群的实际状态和期望状态一致。

声明式配置: Kubernetes 采用声明式配置,用户描述期望的集群状态(例如:部署多少个 Pod、使用多少个副本),然后 Kubernetes 根据这些期望的状态自动进行调整。例如,通过创建一个 Deployment 来指定希望运行的 Pod 副本数,Kubernetes 会确保实际运行的 Pod 数量与期望的副本数保持一致。

控制循环: Kubernetes 的 控制器(Controller)是一个不断运行的后台进程,它监视集群的状态并确保系统的实际状态符合期望状态。控制器通过读取集群的状态,判断是否满足用户指定的目标,若不满足则进行相应的调整。

  • ReplicaSet Controller:如果实际的 Pod 副本数低于期望的副本数,ReplicaSet 会自动启动新的 Pod;如果副本数过多,它会删除多余的 Pod

  • Deployment Controller:管理应用程序的部署过程,当更新时,Kubernetes 会按顺序滚动更新 Pod,以确保无缝地迁移到新版本。

调度与资源分配: Kubernetes 使用调度器(Scheduler)来根据集群资源(如 CPU、内存)和节点的状态将 Pod 调度到合适的节点上。调度器考虑以下因素来选择最合适的节点:

  • 资源请求与限制:每个 · 会请求特定数量的资源(如 · 和内存),调度器会根据节点的资源可用性进行调度。

  • 节点亲和性与反亲和性:通过配置 Node AffinityPod AffinityKubernetes 可以确保 Pod 部署在合适的节点上。

  • TaintsTolerations:用于节点和 Pod 之间的约束,确保 Pod 不会调度到某些特定的节点上。

自动化管理:

  • 滚动更新Kubernetes 支持滚动更新,可以确保在更新应用时不会造成服务中断。例如,使用 Deployment 更新一个应用时,Kubernetes 会先启动新的 Pod 实例,等其就绪后再停止旧的实例,保证应用持续可用。

  • Pod 自动恢复: Kubernetes 通过健康检查(LivenessProbeReadinessProbe)来确保容器健康。如果容器崩溃或无法响应,Kubernetes 会自动重启容器或替换故障 Pod

  • 自动扩展Kubernetes 提供 Horizontal Pod Autoscaler(HPA)Cluster Autoscaler,能够根据 CPU、内存使用率等自动扩展 Pod 数量或节点数量。

1.2 如何使用 Kubernetes 部署 Node 服务?

本文基于 Kubernetes 部署 Node.js 服务,具体方案如下:

  1. 基于 Jenkins 来触发 Node 服务构建, 拉取代码、安装依赖、构建 Docker 镜像,推送镜像到镜像仓库。并且,在测试环境针对构建流水线进行优化,基于 WebHookJenkins Pipeline 代码提交后自动触发构建,减少人工操作。

  2. Secret/ConfigMap: 配置分离,ConfigMap 用于存储服务端非敏感配置数据, Secret 安全管理敏感信息,如数据库密码。在 Deployment template spec 中通过 env valueFromSecret/ConfigMap 映射为环境变量或者通过 volumeMounts 将配置以文件形式挂载到容器内。

  3. Service: 使用 NodePortLoadBalancer 将服务暴露给外部,并结合 ReadinessProbe 确保 Service 仅将流量分发给健康的 Pod, 保证服务稳定和服务高可用。ClusterIP 仅集群内访问,适合内部服务。NodePort 暴露服务给外部,但端口范围有限。LoadBalancer 结合云厂商实现外部访问的负载均衡。Pod 启动后,Kubernetes 会开始执行 ReadinessProbe。如果检查成功,Pod 被标记为 Ready,并被加入到 ServiceEndpoints 列表中,开始接收流量。如果 ReadinessProbe 检查失败,Pod 会从 Endpoints 列表中移除,Service 不再将流量路由到该 Pod,直到探针成功。一旦 ReadinessProbe 成功,Pod 会被重新添加到 Endpoints 列表,开始接收流量。

  4. Ingress: 基于 NGINX Ingress Controller 部署服务,通过路径和域名规则进行流量分发,并结合 cert-manager 自动化申请 TLS 证书,实现安全的 HTTPS 访问。基于路径(如 /api 转发到 API 服务)。基于域名(如 api.example.com 转发到后端服务)。使用 cert-manager 自动化管理 TLS 证书,保障 HTTPS 安全通信。

  5. Deployment: 配置 replicas 使用多副本部署,保证服务不会因单个 Pod 故障而中断。配置 LivenessProbe 自动检测不健康的容器并重启, 使服务具有故障恢复的功能。配置 ReadinessProbe 确保服务准备好接收流量, 做好负载准备。设置 resources 限制资源使用,防止资源耗尽。基于 Pod Anti-AffinitynodeAffinity 将副本分布在不同节点,避免单点故障,做到服务容灾和高可用。Pod 启动后,Kubernetes 会开始执行 ReadinessProbe。如果检查成功,Pod 被标记为 Ready,并被加入到 ServiceEndpoints 列表中,开始接收流量。如果 ReadinessProbe 检查失败,Pod 会从 Endpoints 列表中移除,Service 不再将流量路由到该 Pod,直到探针成功。一旦 ReadinessProbe 成功,Pod 会被重新添加到 Endpoints 列表,开始接收流量。ReadinessProbe 不等同于 LivenessProbeReadinessProbe 是用来检查 Pod 是否准备好接收流量,而 LivenessProbe 用来检查 Pod 是否仍然存活。如果 LivenessProbe 失败,Pod 会被重新启动,但它仍然可能处于 Not Ready 状态,直到 ReadinessProbe 成功。

  6. Horizontal Pod Autoscaler (HPA): 通过 HPA 基于 CPU 和内存使用率动态扩展 Pod,并结合 Prometheus Adapter 支持获取 Prometheus 自定义指标, HPA 使用这些自定义指标来自动扩展 Pod,提升资源利用率。对于突发流量,可与 Cluster Autoscaler 根据 Pod 扩容自动增加或减少节点,联动扩展节点资源以解决节点资源不足的问题。以下是一些 Prometheus 自定义的监控指标:QPS:监控每秒请求数,查看是否达到了扩容阈值。内存使用量:确保在 Pod 扩容后,内存使用量在可控范围内,避免内存泄漏或资源过度使用。节点资源:监控节点的 CPU 和内存使用情况,确保节点资源在扩容时得以充分利用。通过这些监控,可以调整 Prometheus AdapterHPA 配置,确保扩容逻辑符合业务需求,并避免资源浪费。

  7. 日志管理、监控、调优: 使用 PrometheusGrafana 监控服务性能指标,如 CPU内存响应时间 等。同时通过 EFK 日志管理方案,集中存储和分析日志,快速定位问题。自定义指标(如CPU内存使用率响应时间请求错误率)接入 Prometheus, 通过 Grafana 仪表盘实时监控服务状态。Fluentd 采集日志,过滤、转换后发送到 ElasticsearchKibana 用于搜索和可视化日志。

1.3 为什么使用 Kubernetes 部署 Node 服务?

使用 Kubernetes 部署 Node.js 服务,主要是因为它解决了传统服务部署中面临的 资源管理弹性伸缩可用性容器编排 等问题。具体原因包括:

  1. 高可用与自动恢复: Kubernetes 提供 自愈机制,如 LivenessProbeReadinessProbe,当 Node 服务出现异常时,可以自动重启容器,确保服务持续运行,避免人工干预。

  2. 弹性伸缩: 通过 HPA(Horizontal Pod Autoscaler) 和自定义指标(如 QPSCPU/内存使用率),可以实现对 Node 服务的 自动扩容与缩容,满足突发流量需求并提升资源利用率。

  3. 负载均衡与流量管理: 使用 Kubernetes ServiceIngress 可以轻松实现服务的负载均衡和流量分发,保证服务高可用,同时支持基于路径或域名的流量转发。

  4. 配置管理: Kubernetes 提供 ConfigMapSecret,将服务的配置与代码分离,使 Node.js 服务的敏感信息和非敏感配置更加安全、灵活地进行管理。

  5. 容器编排与管理: Kubernetes 可以统一管理多个容器化的 Node 服务,确保其在不同节点上分布均匀,同时提供 Pod Anti-Affinity 避免单点故障,提升服务可靠性。

  6. 自动部署与持续集成/持续部署(CI/CD: 结合 JenkinsHelm 等工具,可以实现 Node 服务的自动构建、打包、部署和更新,提高发布效率,降低手动操作的出错率。

  7. 资源管理与优化: Kubernetes 提供了 资源请求和限制 的功能,可以合理分配 CPU 和内存资源,防止 Node.js 服务耗尽节点资源,导致服务崩溃。Kubernetes 通过 资源限制(Limits资源请求(Requests 确保资源分配合理,Pod 可以根据节点资源动态调度。

  8. 统一监控与日志管理: 使用 PrometheusGrafanaEFK 等工具,可以轻松实现对 Node 服务的监控和日志收集,帮助运维人员快速定位和解决问题。

1.4 Kubernetes 部署 Node Vs PM2 部署 Node

PM2 是一个流行的进程管理工具,专门用于管理 Node.js 应用程序。虽然 PM2 对于 Node.js 应用的部署与管理非常有效,但相比于 KubernetesPM2 更适用于单机环境或者较小的服务集群。以下是二者的主要区别和优势对比:

PM2PM2 主要用于单节点的进程管理,虽然支持多进程模式(通过 pm2 scale 命令),但其本质上是管理本地机器上的 Node.js 进程,不能自动扩展到多个机器,也无法进行跨节点的服务管理。

KubernetesKubernetes 是一个分布式系统平台,能够管理跨多个节点的服务部署、扩展和管理。通过 PodsDeploymentsReplicaSets,可以轻松实现自动扩容、滚动更新、负载均衡等功能,适用于大规模分布式应用。

如果你的应用规模较小,PM2 是一个简单有效的进程管理工具,而如果你面临的是大规模、分布式的 Node 服务部署,Kubernetes 提供的自动化管理、高可用性、资源管理和扩展性优势将显得更加突出。

二、构建镜像


构建 Node.js 服务镜像

2.1 目录结构

backend-server 

│── node_modules
│── .dockerignore
│── Dockerfile
│── index.js
└── package.json

2.2 index.js

const Koa = require("koa");
const KoaRouter = require("koa-router");

const port = 3000;
const app = new Koa();
const router = new KoaRouter();

const PORT = process.env.APP_PORT || 3000;
const DB_PASSWORD = process.env.DB_PASSWORD;

console.log("------PORT------", PORT);
console.log("-------DB_PASSWORD--------", DB_PASSWORD);

router.get("/", async (ctx) => {
ctx.body = {
code: 200,
message: "Hello Koa Server 001",
};
});

app.use(router.routes()).use(router.allowedMethods());

app.listen(port, () => {
console.log(`Koa HTTP Server is running on port ${port}`);
});

访问环境变量: 通过 process.env 可以访问 Dockerfile 以及 Deployment env 环境变量。在 Kubernetes 部署中,环境变量的优先级如下:

  • ConfigMapSecret 注入的环境变量(最高优先级)

  • 容器镜像(Dockerfile)中的 ENV 默认值

  • 应用程序代码中的默认值(如 || 3000

2.3 Dockerfile

FROM registry.cn-hangzhou.aliyuncs.com/bolawen/node:22.7.0-linux-arm64
WORKDIR /server
COPY package*.json /server
RUN npm install --registry=https://registry.npmmirror.com
COPY . /server

ENV APP_ENV=test
ENV APP_PORT=3000
ENV DB_PASSWORD=defaultpassword

EXPOSE 3000
CMD ["node","index.js"]

ENV: ENV 指令设置镜像的默认环境变量值。在 Kubernetes 中运行时,Deployment env 的环境变量会覆盖 Dockerfile 中的默认值。

2.4 .dockerignore

dist
node_modules

2.5 构建并推送镜像

# 构建镜像 
docker build -t backend-server:1.0.0 .

# 推送镜像
docker tag backend-server:1.0.0 registry.cn-hangzhou.aliyuncs.com/bolawen/backend-server:1.0.0
docker push registry.cn-hangzhou.aliyuncs.com/bolawen/backend-server:1.0.0

三、部署服务


3.1 Service

创建 backend-server-service.yaml 文件

apiVersion: v1
kind: Service
metadata:
name: backend-server-service
spec:
type: NodePort
selector:
app: backend-server
ports:
- protocol: TCP
port: 3000
targetPort: 3000

流量分发Service 会自动将流量分发给多个 Pod,保证高可用。

对外访问NodePort 允许外部访问服务。

端口映射:将外部请求端口映射到容器内部服务端口。

3.2 Ingress

创建 backend-server-ingress.yaml 文件

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nodejs-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: nodejs.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nodejs-service
port:
number: 80

域名管理:通过域名(nodejs.example.com)访问服务,更加友好。

负载均衡Ingress 可以处理负载均衡,分发流量给多个 Pod

灵活路由:支持路径规则,例如将 /api 路径转发到特定服务。

3.3 Deployment

创建 backend-server-deployment.yaml 文件

apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-server-deployment
spec:
replicas: 2
selector:
matchLabels:
app: backend-server
template:
metadata:
labels:
app: backend-server
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-type
operator: In
values:
- worker
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nodejs
topologyKey: "kubernetes.io/hostname"
containers:
- name: backend-server
image: registry.cn-hangzhou.aliyuncs.com/bolawen/backend-server:1.0.3
ports:
- containerPort: 3000
env:
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: backend-server-config-map
key: APP_ENV
- name: APP_PORT
valueFrom:
configMapKeyRef:
name: backend-server-config-map
key: APP_PORT
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: backend-server-secret
key: DB_PASSWORD
resources:
requests:
cpu: "250m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 5
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5

高可用:使用多副本部署,保证服务不会因单个 Pod 故障而中断。

故障恢复LivenessProbe 自动检测不健康的容器并重启。

负载准备ReadinessProbe 确保服务准备好接收流量。

资源管理:设置 resources 限制资源使用,防止资源耗尽。

容灾Pod Anti-Affinity 将副本分布在不同节点,避免单点故障。

3.4 Development Secret

创建 backend-server-secret.yaml 文件

apiVersion: v1
kind: Secret
metadata:
name: backend-server-secret
type: Opaque
data:
DB_PASSWORD: cm9vdA==

使用 echo -n "your_value" | base64 将值编码成 base64

3.5 Development ConfigMap

创建 backend-server-config-map.yaml 文件

apiVersion: v1
kind: ConfigMap
metadata:
name: backend-server-config-map
data:
APP_PORT: "3000"
APP_ENV: "test"

3.6 Horizontal Pod Autoscaler (HPA)

通过 HPA 基于 CPU 和内存使用率动态扩展 Pod,并结合 Prometheus Adapter 支持自定义指标扩展,提升资源利用率。对于突发流量,可与 Cluster Autoscaler 联动扩展节点资源。使用 Prometheus Adapter 实现自定义监控指标(如 QPS内存 等)的 HPA 扩展。结合 Cluster Autoscaler 扩展节点,解决 Pod 扩容后节点资源不足的问题。

1. 配置 HPA 基于 CPU 和内存使用率动态扩展 Pod

创建 backend-server-hpa.yaml 配置文件

apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: my-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: backend-server-deployment
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50

2. 安装和配置 Prometheus Adapter: Prometheus Adapter 允许 Kubernetes Horizontal Pod Autoscaler (HPA) 基于自定义指标(如 QPS、内存等)进行扩容。它将 Prometheus 中收集到的自定义指标映射到 Kubernetes 内部指标,供 HPA 使用。Prometheus Adapter 将会将 Prometheus 中的监控指标暴露给 HPA 使用。配置 prometheus-adapter 以便它能够获取 Prometheus 中的自定义指标。你可以通过修改 prometheus-adapterConfigMap 来指定 Prometheus 查询规则(PromQL),并将这些指标暴露给 Kubernetes API

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

# 安装 Prometheus Adapter
helm install prometheus-adapter prometheus-community/prometheus-adapter \
--namespace monitoring

创建 backend-server-prometheus-adapter-config-map.yaml 文件

apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-adapter-config
namespace: monitoring
data:
config.yaml: |
rules:
- seriesQuery: 'http_requests_total{job="api"}'
resources:
overrides:
namespace:
resource: namespace
pod:
resource: pod
name:
matches: "^http_requests_total$"
as: "qps"
metricsQuery: 'sum(rate(http_requests_total{job="api"}[1m])) by (pod)'
- seriesQuery: 'container_memory_usage_bytes{container!="",pod!="",job="kubelet"}'
resources:
overrides:
pod:
resource: pod
name:
matches: "^container_memory_usage_bytes$"
as: "memory_usage_bytes"
metricsQuery: 'sum(container_memory_usage_bytes{job="kubelet",pod=~".*"}) by (pod)'

在上面的配置中,我们定义了两个指标:

qps:基于 http_requests_total 度量计算每秒请求数。

memory_usage_bytes:获取 Pod 内存使用量。

3. 为 HPA 配置自定义指标: 在 Prometheus Adapter 配置好之后,你可以在 HPA 的配置中引用这些自定义指标。下面是一个 HPA 配置示例,基于 qpsmemory_usage_bytes 来调整 Pod 副本数。

apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: node-service-hpa
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: node-service
minReplicas: 1
maxReplicas: 10
metrics:
- type: External
external:
metric:
name: qps
selector:
matchLabels:
pod: "node-service"
target:
type: AverageValue
averageValue: "100" # 设置 QPS 达到 100 时扩容
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80 # 内存使用率达到 80% 时扩容

在上面的示例中:

qps:当 QPS 达到 100 时,HPA 会触发扩容。

memory:当内存使用率达到 80% 时,HPA 会触发扩容。

  1. 安装和配置 Cluster Autoscaler: Cluster Autoscaler 是一个 Kubernetes 集群组件,用于自动增加或减少集群中的节点数。如果 HPA 扩容导致资源需求超过当前节点的容量,Cluster Autoscaler 会自动扩展节点以满足新增加的 Pod 的资源需求。

Cluster Autoscaler 可以通过 Helm 安装或直接使用 KubernetesYAML 配置进行部署。

helm repo add autoscaler https://kubernetes.github.io/autoscaler
helm repo update
helm install cluster-autoscaler autoscaler/cluster-autoscaler \
--namespace kube-system \
--set autoDiscovery.clusterName=<your-cluster-name> \
--set awsRegion=<your-region> # 根据你的云提供商和区域配置

Cluster Autoscaler 根据节点资源和 Pod 的需求来决定是否需要扩展集群。你需要确保集群节点支持扩容,且各节点具备合适的资源限制和配额。Cluster Autoscaler 的配置会根据以下规则自动扩展集群:

  • HPA 扩容 Pod 时,如果现有节点无法满足新增 Pod 的资源需求,Cluster Autoscaler 会自动添加更多节点。

  • 当某些 Pod 被缩减时,如果某些节点上的 Pod 不再需要资源,Cluster Autoscaler 会自动删除空闲节点以节省资源。

3.7 应用以上配置文件

kubectl apply -f backend-server-config-map.yaml
kubectl apply -f backend-server-secret.yaml
kubectl apply -f backend-server-service.yaml
kubectl apply -f backend-server-deployment.yaml

四、访问服务


4.1 检测资源状态

# 查看 Pod 状态
kubectl get pods

# 查看 Deployment 状态
kubectl get deployments

# 查看 Service 状态
kubectl get services

4.2 NodePort 访问服务

1. 通过 kubectl describe pod [Pod Name] 获取 NodeIP

kubectl describe pod [Pod Name]

# 输出

Name: backend-server-deployment-64df744d7c-mcns7
Namespace: default
Priority: 0
Service Account: default
Node: k8s-node1/192.168.105.133
Start Time: Wed, 04 Dec 2024 20:16:41 +0800
Labels: app=backend-server

2. 通过 kubectl get services 获取 NodePort 端口

kubectl get services

# 输出
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
node-app-service NodePort 10.108.73.245 <none> 80:30007/TCP 2m

3. 使用集群节点的 IP 地址访问服务

http://<Node-IP>:<NodePort>

# 比如:
http://192.168.105.133:32490/