跳到主要内容

19、Kubernetes - 实战:使用 Deployment、Service、Nginx Ingress Controller 搭建 MariaDB+Nginx+WordPress

1. 知识点回顾

kubeadm 使用容器技术封装了 Kubernetes 组件,所以只要节点上安装了容器运行时(Dockercontainerd 等),它就可以自动从网上拉取镜像,然后以容器的方式运行组件,非常简单方便。

Deployment 是用来管理 Pod 的一种对象,它代表了运维工作中最常见的一类在线业务,在集群中部署应用的多个实例,而且可以很容易地增加或者减少实例数量,从容应对流量压力。

Deployment 的定义里有两个关键字段:一个是 replicas,它指定了实例的数量;另一个是 selector,它的作用是使用标签“筛选”出被 Deployment 管理的 Pod,这是一种非常灵活的关联机制,实现了 API 对象之间的松耦合。

DaemonSet 是另一种部署在线业务的方式,它很类似 Deployment,但会在集群里的每一个节点上运行一个 Pod 实例,类似 Linux 系统里的“守护进程”,适合日志、监控等类型的应用。

DaemonSet 能够任意部署 Pod 的关键概念是“污点”(taint)和“容忍度”(toleration)。Node 会有各种“污点”,而 Pod 可以使用“容忍度”来忽略“污点”,合理使用这两个概念就可以调整 Pod 在集群里的部署策略。

DeploymentDaemonSet 部署的 Pod,在集群中处于“动态平衡”的状态,总数量保持恒定,但也有临时销毁重建的可能,所以 IP 地址是变化的,这就为微服务等应用架构带来了麻烦。

Service 是对 Pod IP 地址的抽象,它拥有一个固定的 IP 地址,再使用 iptables 规则把流量负载均衡到后面的 Pod,节点上的 kube-proxy 组件会实时维护被代理的 Pod 状态,保证 Service 只会转发给健康的 Pod

Service 还基于 DNS 插件支持域名,所以客户端就不再需要关心 Pod 的具体情况,只要通过 Service 这个稳定的中间层,就能够访问到 Pod 提供的服务。

Service 是四层的负载均衡,但现在的绝大多数应用都是 HTTP/HTTPS 协议,要实现七层的负载均衡就要使用 Ingress 对象。

Ingress 定义了基于 HTTP 协议的路由规则,但要让规则生效,还需要 Ingress ControllerIngress Class 来配合工作。

  • Ingress Controller 是真正的集群入口,应用 Ingress 规则调度、分发流量,此外还能够扮演反向代理的角色,提供安全防护、TLS 卸载等更多功能。
  • Ingress Class 是用来管理 Ingress 和 Ingress Controller 的概念,方便我们分组路由规则,降低维护成本。

不过Ingress Controller 本身也是一个 Pod,想要把服务暴露到集群外部还是要依靠 ServiceService 支持 NodePortLoadBalancer 等方式,但 NodePort 的端口范围有限,LoadBalancer 又依赖于云服务厂商,都不是很灵活。

折中的办法是用少量 NodePort 暴露 Ingress Controller,用 Ingress 路由到内部服务,外部再用反向代理或者 LoadBalancer 把流量引进来。

2. WordPress 网站基本架构

既然我们已经掌握了 DeploymentServiceIngress 这些 Pod 之上的概念,网站自然会有新变化,架构图我放在了这里:
 
这次的部署形式比起 Dockerminikube 又有了一些细微的差别,重点是我们已经完全舍弃了 Docker,把所有的应用都放在 Kubernetes 集群里运行,部署方式也不再是裸 Pod,而是使用 Deployment,稳定性大幅度提升。

原来的Nginx 的作用是反向代理,那么在 Kubernetes 里它就升级成了具有相同功能的 Ingress ControllerWordPress 原来只有一个实例,现在变成了两个实例(你也可以任意横向扩容),可用性也就因此提高了不少。而 MariaDB 数据库因为要保证数据的一致性,暂时还是一个实例。

还有,因为 Kubernetes 内置了服务发现机制 Service,我们再也不需要去手动查看 PodIP 地址了,只要为它们定义 Service 对象,然后使用域名就可以访问 MariaDBWordPress 这些服务。

网站对外提供服务我选择了两种方式。

  • 一种是让 WordPress 的 Service 对象以 NodePort 的方式直接对外暴露端口 30088,方便测试;
  • 一种是给 Nginx Ingress Controller 添加 hostNetwork属性,直接使用节点上的端口号,类似 Docker 的 host 网络模式,好处是可以避开 NodePort 的端口范围限制。

3. 部署 MariaDB

先要用ConfigMap 定义数据库的环境变量,有 DATABASEUSERPASSWORDROOT_PASSWORD

# maria-cm.yml

apiVersion: v1
kind: ConfigMap
metadata:
  name: maria-cm

data:
  DATABASE: 'db'
  USER: 'wp'
  PASSWORD: '123'
  ROOT_PASSWORD: '123'

然后我们需要把 MariaDBPod 改成 Deployment 的方式,replicas 设置成 1 个,template 里面的 Pod 部分没有任何变化,还是要用 envFrom把配置信息以环境变量的形式注入 Pod,相当于把 Pod 套了一个 Deployment 的“外壳”:

# maria-dep.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: maria-dep
  name: maria-dep

spec:
  replicas: 1
  selector:
    matchLabels:
      app: maria-dep

  template:
    metadata:
      labels:
        app: maria-dep
    spec:
      containers:
      - image: mariadb:10
        name: mariadb
        ports:
        - containerPort: 3306

        envFrom:
        - prefix: 'MARIADB_'
          configMapRef:
            name: maria-cm

我们还需要再为 MariaDB 定义一个Service 对象,映射端口 3306,让其他应用不再关心 IP 地址,直接用 Service 对象的名字来访问数据库服务:

# maria-svc.yml

apiVersion: v1
kind: Service
metadata:
  labels:
    app: maria-dep
  name: maria-svc

spec:
  ports:
  - port: 3306
    protocol: TCP
    targetPort: 3306
  selector:
    app: maria-dep

因为这三个对象都是数据库相关的,所以可以在一个 YAML 文件里书写,对象之间用 --- 分开,这样用 kubectl apply 就可以一次性创建好:


kubectl apply -f mari-cm-dep-svc.yml

执行命令后,你应该用 kubectl get 查看对象是否创建成功,是否正常运行:
 

4. 部署 WordPress

因为刚才创建了 MariaDBService,所以在写 ConfigMap 配置的时候 HOST就不应该是 IP 地址了,而应该是 DNS 域名,也就是 Service 的名字 maria-svc,这点需要特别注意:


apiVersion: v1
kind: ConfigMap
metadata:
  name: wp-cm

data:
  HOST: 'maria-svc'
  USER: 'wp'
  PASSWORD: '123'
  NAME: 'db'

WordPressDeployment 写法和 MariaDB 也是一样的,给 Pod 套一个 Deployment 的“外壳”,replicas 设置成 2 个,用字段 envFrom配置环境变量:


apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: wp-dep
  name: wp-dep

spec:
  replicas: 2
  selector:
    matchLabels:
      app: wp-dep

  template:
    metadata:
      labels:
        app: wp-dep
    spec:
      containers:
      - image: wordpress:5
        name: wordpress
        ports:
        - containerPort: 80

        envFrom:
        - prefix: 'WORDPRESS_DB_'
          configMapRef:
            name: wp-cm

然后我们仍然要为 WordPress 创建 Service 对象,这里我使用了 NodePort类型,并且手工指定了端口号“30088”(必须在 30000~32767 之间):


apiVersion: v1
kind: Service
metadata:
  labels:
    app: wp-dep
  name: wp-svc

spec:
  ports:
  - name: http80
    port: 80
    protocol: TCP
    targetPort: 80
    nodePort: 30088

  selector:
    app: wp-dep
  type: NodePort

现在让我们用 kubectl apply 部署 WordPress


kubectl apply  -f  wp-cm-dep-svc.yml

这些对象的状态可以从下面的截图看出来:

 
因为WordPressService 对象是 NodePort 类型的,我们可以在集群的每个节点上访问 WordPress 服务。

比如一个节点的 IP 地址是“192.168.10.210”,那么你就在浏览器的地址栏里输入“http://192.168.10.210:30088”,其中的“30088”就是在 Service 里指定的节点端口号,然后就能够看到 WordPress 的安装界面了:

 

5. 部署 Nginx Ingress Controller

首先我们需要定义 Ingress Class,名字就叫 wp-ink,非常简单:

# wp-ink.yml

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: wp-ink

spec:
  controller: nginx.org/ingress-controller

然后用kubectl create 命令生成 Ingress 的样板文件,指定域名是 wp.test,后端 Servicewp-svc:80Ingress Class 就是刚定义的 wp-ink


kubectl create ing wp-ing --rule="wp.test/=wp-svc:80" --class=wp-ink $out

得到的Ingress YAML 就是这样,注意路径类型我还是用的前缀匹配 Prefix

# wp-ing.yml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: wp-ing

spec:
  ingressClassName: wp-ink

  rules:
  - host: wp.test
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: wp-svc
            port:
              number: 80

接下来就是最关键的 Ingress Controller 对象了,它仍然需要从 Nginx 项目的示例 YAML 修改而来,要改动名字、标签,还有参数里的 Ingress Class

在之前讲基本架构的时候我说过了,这个 Ingress Controller 不使用 Service,而是给它的 Pod 加上一个特殊字段 hostNetwork,让 Pod 能够使用宿主机的网络,相当于另一种形式的 NodePort

# wp-kic-dep.yml

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wp-kic-dep
  namespace: default

spec:
  replicas: 1
  selector:
    matchLabels:
      app: wp-kic-dep

  template:
    metadata:
      labels:
        app: wp-kic-dep
    annotations:
      prometheus.io/scrape: "true"
      prometheus.io/port: "9113"
      prometheus.io/scheme: http
    spec:
      serviceAccountName: default

      use host network
      hostNetwork: true
      dnsPolicy: ClusterFirstWithHostNet

      containers:
     - image: nginx/nginx-ingress:2.2.0
      - image: nginx/nginx-ingress:2.2-alpine
        imagePullPolicy: IfNotPresent
        name: nginx-ingress
        ports:
        - name: http
          containerPort: 80
        - name: https
          containerPort: 443
        - name: readiness-port
          containerPort: 8081
        - name: prometheus
          containerPort: 9113
        readinessProbe:
          httpGet:
            path: /nginx-ready
            port: readiness-port
          periodSeconds: 1
        securityContext:
          allowPrivilegeEscalation: true
          runAsUser: 101nginx
          capabilities:
            drop:
            - ALL
            add:
            - NET_BIND_SERVICE
        env:
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        args:
          - -ingress-class=wp-ink
          - -health-status
          - -ready-status
          - -nginx-status
          - -enable-snippets

          - -nginx-configmaps=$(POD_NAMESPACE)/nginx-config
          - -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret
        - -v=3 Enables extensive logging. Useful for troubleshooting.
        - -report-ingress-status
        - -external-service=nginx-ingress
        - -enable-prometheus-metrics
        - -global-configuration=$(POD_NAMESPACE)/nginx-configuration

---

apiVersion: v1
kind: Service
metadata:
  name: wp-kic-svc
  namespace: default

spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
    nodePort: 30080

  selector:
    app: wp-kic-dep
  type: NodePort

准备好Ingress 资源后,将上面 4 个 yml文件整合成一个 wp-ing-ingclass-ingcontroller.yml:

kubectl  create namespace nginx-ingress
kubectl apply -f wp-ing-ingclass-ingcontroller.yml

备注:实践过程发现 nginx/nginx-ingress:2.2-alpine 这个镜像一直无法下载,待继续研究。

现在所有的应用都已经部署完毕,可以在集群外面访问网站来验证结果了

不过你要注意,Ingress 使用的是 HTTP 路由规则,用 IP 地址访问是无效的,所以在集群外的主机上必须能够识别我们的 wp.test域名,也就是说要把域名 wp.test解析到 Ingress Controller 所在的节点上。

如果你用的是 Mac/Linux,那就修改 /etc/hosts ,添加一条解析规则就行:


cat /etc/hosts
192.168.10.210  wp.test

有了域名解析,在浏览器里你就不必使用 IP 地址,直接用域名 wp.testIngress Controller 就能访问我们的 WordPress 网站了:
 
今天我们还在 Kubernetes 集群里再次搭建了 WordPress 网站,应用了新对象 DeploymentServiceIngress,为网站增加了横向扩容、服务发现和七层负载均衡这三个非常重要的功能,提升了网站的稳定性和可用性,基本上解决了在“初级篇”所遇到的问题。

虽然这个网站离真正实用还差得比较远,但框架已经很完善了,你可以在这个基础上添加其他功能,比如创建证书 Secret、让 Ingress 支持 HTTPS 等等。

另外,我们保证了网站各项服务的高可用,但对于数据库 MariaDB 来说,虽然 Deployment 在发生故障时能够及时重启 Pod,新 Pod 却不会从旧 Pod 继承数据,之前网站的数据会彻底消失,这个后果是完全不可接受的。