K8s集群搭建完成后,真正完成我们业务的是那些跑在k8s上的pod们。将业务跑在k8s集群只上,我们可以实现根据负载或者资源利用率动态扩容或者缩容我们的后端服务器,更加灵活高效的利用我们的物理设备,且能够实现服务的高可用及故障自治愈,本文将详细介绍以上的具体实现。

实验环境

  本次演示使用主机系统均为ubuntu1804。

节点IP
master节点192.168.32.18、192.168.32.19
node节点192.168.32.21、192.168.32.22
etcd192.168.32.23、192.168.32.24、192.168.32.25
harbor192.168.32.20
NFS服务器192.168.32.20(复用)

  如果机器不够,可以选择复用,例如将etcd的三台主机与master主机和harbor主机复用,可以省下来3台主机。推荐node节点的性能高一些,因为一般之后的pod都将运行在node节点上,如果node节点的资源不足,则很有可能无法创建pod,导致服务启动或者扩容失败。
  具体K8s集群的搭建,可以参考我之前文章使用kubeasz自动化部署K8s。harbor服务器的搭建和部署配置,也可以参考我之前文章Docker(五)——Docker镜像仓库
  因为是均为内网环境,建议将网络组件calico的IPIP模式(ip-in-ip叠加模式)关掉,使用calico的BGP模式,以节约大量主机内部访问时封装的性能损耗。具体操作可以参考之前博客使用kubeasz自动化部署K8s

设计架构

  我们设计一个简单架构为例,如下图所示。
k8s架构-java服务集群
  一般负载层是使用物理机上的haproxy服务来实现4层负载,而不是在K8s集群内部pod 实现。我们这里也就将他省略不再细说。
所以我们需要创建一个nginx集群,以及一个tomcat集群。将动态资源,转发给后端的tomcat服务,前端的静态页面由nginx来处理。为了方便我们对后端服务的修改以及部署,我们还需要一个NFS服务器,来共享静态资源以及动态资源,以实现后端服务数据的实时同步。

搭建业务镜像架构

  镜像一般是按层次一级一级实现的,可实现多个业务复用。所以,例如我们将要实现的业务为app1,则我们需要的的镜像,就有对应的app1-nginx及app1-tomcat的业务镜像,以及nginx及tomcat的基础镜像,以及基础系统镜像,我们要一层层的来制作镜像。详细可以参考之前文章Docker(三)——镜像制作

cd /opt
mkdir -p k8s/{app1,dockerfile,yaml}
cd k8s/dockerfile
mkdir {app1,pub-image,system}

  最终结构如下图所示。

root@DockerUbuntu18:/opt/k8s/dockerfile# tree -d
.
├── app1
│   ├── app1-nginx
│   └── app1-tomcat
├── pub-images
│   ├── jdk
│   │   └── 8
│   ├── nginx-base
│   └── tomcat-base
└── system
    └── centos

  打好的镜像如下所示。

root@DockerUbuntu18:/opt/k8s/dockerfile# docker images
REPOSITORY                                          TAG                 IMAGE ID            CREATED             SIZE
harbor.micepro.net/base/app1-tomcat                  v1                  c7d87d05f8a4        3 days ago          1.19GB
harbor.micepro.net/base/tomcat-base                  v8.5.47             12cf3e12f2d0        3 days ago          1.19GB
harbor.micepro.net/base/jdk-base                     v8.212              1582b1e9cfe4        3 days ago          1.16GB
harbor.micepro.net/base/app1-nginx                   v1                  4836f9aed79b        3 days ago          940MB
harbor.micepro.net/base/nginx-base                   v1.16.1             bfc05a5c23ed        5 days ago          940MB
harbor.micepro.net/base/centos-base                  v7.6                bc7e922a8352        5 days ago          753MB
harbor.micepro.net/base/centos                       base                f1cb7c7d58b7        10 months ago       202MB

配置NFS服务器

  我们计划将动态资源和静态资源都放置至我们的NFS共享存储上,也就是我们的harbor上(任意主机,端口不冲突就可以~)
  可以通过包管理工具快速安装一个NFS服务,毕竟这只是一个辅助工具,无需过多关注。

apt install -y nfs-server
mkdir /data/app1/{images,static,ROOT} -p
echo "/data/app1 *(rw,no_root_squash)" >> /etc/exports
systemctl enable --now nfs-server

  将/data/app1目录整个共享出去即可,之后我们可以直接将NFS挂载至pod中,也可以通过PV/PVC的方式挂载到pod中。
  我们需要修改权限,通过指定uid的方式,使某个UID在多个主机之间都具有权限。这就要求我们之前在创建基础镜像时,也使用的是相同的UID启动程序。例如我们统一都使用www用户,指定UID为2020。(之前创建的容器以及基础镜像中都要修改服务以UID为2020的www用户启动)

编写yaml文件

  此时我们就可以开始着手准备yaml文件,来规划我们的服务pod了。

namespace

  首先我们需要新创建一个namespace,来实现与其他服务隔离。

cd /opt/k8s/
mkdir -p yaml/namespace
vim yaml/namespace/app1.yaml
apiVersion: v1 #API版本
kind: Namespace #类型为namespac
metadata: #定义元数据
  name: app1 #namespace名称

  然后将这个namespace创建出来,使用命令

kubectl apply -f /opt/k8s/yaml/namespace/app1.yaml

  此时使用命令kubectl get ns可以看到我们新创建的namespace

root@DockerUbuntu18:/opt/k8s# kubectl get ns
NAME              STATUS   AGE
app1              Active   20s
default           Active   3d
kube-node-lease   Active   3d
kube-public       Active   3d
kube-system       Active   3d

pod

  先创建好对应的目录,方便后期管理维护。

cd /opt/k8s
mkdir -p app1/{nginx,tomcat}

  然后我们就可以编写pod的yaml文件了。

cd /opt/k8s/app1/nginx

  可以将Deploymentserver写在一起,也可以分开写。我们这直接都写在一起。

vim nginx.yaml

kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  labels:
    app: app1-nginx-deployment-label
  name: app1-nginx-deployment
  namespace: app1
spec:
  replicas: 2  #设置nginx集群数量
  selector:
    matchLabels:
      app: app1-nginx-selector
  template:
    metadata:
      labels:
        app: app1-nginx-selector
    spec:
      containers:
      - name: app1-nginx-container
        image: harbor.micepro.net/base/app1-nginx:v1
        #command: ["/apps/tomcat/bin/run_tomcat.sh"]
        imagePullPolicy: IfNotPresent
        #imagePullPolicy: Always
        ports:
        - containerPort: 80
          protocol: TCP
          name: http
        - containerPort: 443
          protocol: TCP
          name: https
        env:
        - name: "password"
          value: "123456"
        - name: "age"
          value: "18"
        resources:
          limits:
            cpu: 2
            memory: 2Gi
          requests:
            cpu: 500m
#            memory: 1Gi
            memory: 200m

        volumeMounts:
        - name: app1-images
          mountPath: /usr/local/nginx/html/webapp/images
          readOnly: false
        - name: app1-static
          mountPath: /usr/local/nginx/html/webapp/static
          readOnly: false
      volumes:
      - name: app1-images
        nfs:
          server: 192.168.32.20
          path: /data/app1/images
      - name: app1-static
        nfs:
          server: 192.168.32.20
          path: /data/app1/static

---
kind: Service
apiVersion: v1
metadata:
  labels:
    app: app1-nginx-service-label
  name: app1-nginx-service
  namespace: app1
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
    nodePort: 30080
  - name: https
    port: 443
    protocol: TCP
    targetPort: 443
    nodePort: 30443
  selector:
    app: app1-nginx-selector
cd /opt/k8s/app1/tomcat

  编写tomcat的pod的yaml文件

kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  labels:
    app: app1-tomcat-app1-deployment-label
  name: app1-tomcat-app1-deployment
  namespace: app1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app1-tomcat-app1-selector
  template:
    metadata:
      labels:
        app: app1-tomcat-app1-selector
    spec:
      containers:
      - name: app1-tomcat-app1-container
        image: harbor.micepro.net/base/tomcat-app1:v1
        command: ["/usr/local/tomcat/bin/run_tomcat.sh"]
        #imagePullPolicy: IfNotPresent
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
          protocol: TCP
          name: http
        env:
        - name: "password"
          value: "123456"
        - name: "age"
          value: "18"
        resources:
          limits:
            cpu: 2
            memory: "1000Mi"
          requests:
            cpu: 500m
            memory: "200Mi"

        volumeMounts:
        - name: app1-myapp
          mountPath: /data/tomcat/webapps/ROOT
          readOnly: false

      volumes:
      - name: app1-myapp
        nfs:
          server: 192.168.32.19
          path: /data/app1/ROOT
      - name: app1-static
        nfs:
          server: 192.168.32.19
          path: /data/app1/static
---
kind: Service
apiVersion: v1
metadata:
  labels:
    app: app1-tomcat-app1-service-label
  name: app1-tomcat-app1-service
  namespace: app1
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 8080
    nodePort: 38080
  selector:
    app: app1-tomcat-app1-selector

修改配置文件

  因为要实现一个通用的nginx+tomcat动静分离web架构,即用户访问的静态页面和图片在由nginx直接响应,而动态请求则基于tomcat的service name转发用户请求到tomcat业务app。
  所以我们需要修改当时创建镜像的nginx的配置文件,配置upstream服务器为tomcat的pod,配置如下

upstream tomcat_webserver {
    server app1-tomcat-app1-service.app1.svc.app1.local:8080;
}
server {
    location /myapp {
        proxy_pass http://tomcat_webserver;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

  其中pod间通信是基于K8s内部DNS实现的,一般是coreDNS或者kubeDNS,如果没有安装是无法解析的,可以使用kubeasz的第七个playbook安装,可参考之前文章使用kubeasz自动化部署K8s
  主机名默认组成结构为release-name-rancher.default.svc.cluster.local,也就是ServerName.NameSpace.svc.CLUSTER_DNS_DOMAINServerName是我们在yaml文件中定义的,NameSpace是我们创建pod时选择的namespaceCLUSTER_DNS_DOMAIN是我们在创建K8s集群时设置的集群名,如果是用kubeasz工具创建的K8s集群,可以去/etc/ansible/hosts文件中查看。
  我们可以在pod间测试通信,确保相互之间可以通过主机名通信。

[root@blog-tomcat-app1-deployment-5b4845ddd8-rr5dq /]# ping app1-nginx-service.app1.svc.cluster.local
PING app1-nginx-service.app1.svc.cluster.local (10.68.101.60) 56(84) bytes of data.
64 bytes from app1-nginx-service.app1.svc.cluster.local (10.68.101.60): icmp_seq=1 ttl=64 time=0.103 ms
64 bytes from app1-nginx-service.app1.svc.cluster.local (10.68.101.60): icmp_seq=2 ttl=64 time=0.314 ms
64 bytes from app1-nginx-service.app1.svc.cluster.local (10.68.101.60): icmp_seq=3 ttl=64 time=0.077 ms

  而且nginx及tomcat中最好将日志格式改为json格式,方便我们收集日志。
  tomcat的配置文件/usr/local/tomcat/conf/server.xml,设置appBase="/data/tomcat/webapps/,日志格式修改如下

        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="{&quot;clientip&quot;:&quot;%h&quot;,&quot;ClientUser&quot;:&quot;%l&quot;,&quot;authenticated&quot;:&quot;%u&quot;,&quot;AccessTime&quot;:&quot;%t&quot;,&quot;method&quot;:&quot;%r&quot;,&quot;status&quot;:&quot;%s&quot;,&quot;SendBytes&quot;:&quot;%b&quot;,&quot;Query?string&quot;:&quot;%q&quot;,&quot;partner&quot;:&quot;%{Referer}i&quot;,&quot;AgentVersion&quot;:&quot;%{User-Agent}i&quot;}" />

PV/PVC

  我们还可以通过创建一个创建PV/PVC的方式的方式来代替直接NFS挂载Volume。
  PersistentVolume(PV)是集群中由管理员配置的一段网络存储。 它是集群中的资源,就像节点是集群资源一样。 PV是容量插件,如Volumes,但其生命周期独立于使用PV的任何单个pod。 此API对象捕获存储实现的详细信息,包括NFS,iSCSI或特定于云提供程序的存储系统。
  PersistentVolumeClaim(PVC)是由用户进行存储的请求。 它类似于pod。 Pod消耗节点资源,PVC消耗PV资源。Pod可以请求特定级别的资源(CPU和内存)。声明可以请求特定的大小和访问模式(例如,可以一次读/写或多次只读)。
  PVC和PV是一一对应的。Persistent Volume相对独立于Pods,单独创建。
  Persistent Volume对具体的存储进行配置和分配,而Pods等则可以使用Persistent Volume抽象出来的存储资源,不需要知道集群的存储细节。
  Persistent Volume和Persistent Volume Claim类似Pods和Nodes的关系,创建Pods需要消耗一定的Nodes的资源。而Persistent Volume则是提供了各种存储资源,而Persistent Volume Claim提出需要的存储标准,然后从现有存储资源中匹配或者动态建立新的资源,最后将两者进行绑定。
  以我们这次的演示为例,PV和PVC如下所示。
  PV的yaml文件示例

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs
spec:
  storageClassName: manual
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteMany
  nfs:
    server: 192.168.32.19
    path: "/data/app1/static"

  PVC的yaml文件示例

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: manual
  resources:
    requests:
      storage: 10Gi

  pod中引用PVC

apiVersion: v1
kind: Pod
metadata:
  name: testpv
  labels:
    role: web-frontend
spec:
  containers:
  - name: web
    image: nginx
    ports:
      - name: web
        containerPort: 80
    volumeMounts:
        - name: nfs
          mountPath: "/usr/local/nginx/html/webapp/static"
  volumes:
  - name: nfs
    persistentVolumeClaim:
      claimName: nfs

HPA

  虽然我们可以使用命令kubectl scale对运行在k8s 环境中的pod 数量进行扩容(增加)或缩容(减小)。

root@DockerUbuntu18:~# kubectl --help | grep -w scale
  scale          Set a new size for a Deployment, ReplicaSet, Replication Controller, or Job
root@DockerUbuntu18:~# kubectl get deployments -n app1
NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
app1-nginx-deployment    1/1     1            1           9d
app1-tomcat-deployment   1/1     1            1           3d4h
root@DockerUbuntu18:~# kubectl scale deployment/app1-tomcat-deployment --replicas=2 -n app1
deployment.extensions/app1-tomcat-deployment scaled
root@DockerUbuntu18:~# kubectl get deployments -n app1
NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
app1-nginx-deployment    1/1     1            1           9d
app1-tomcat-deployment   2/2     2            2           74s

  不过在实际生产中,我们肯定没法随时根据负载或者服务需要,手动动态伸缩我们的后端服务器数量,但是我们可以通过命令kubectl autoscale自动控制在k8s集群中运行的pod数量(水平自动伸缩),想要实现自动上伸缩,需要我们提前设置pod范围及触发条件。
  k8s从1.1版本开始增加了名称为HPA(Horizontal Pod Autoscaler)的控制器,用于实现基于pod中资源(CPU/Memory)利用率进行对pod的自动扩缩容功能的实现,早期的版本只能基于Heapster组件实现对CPU利用率做为触发条件,但是在k8s 1.11版本开始使用Metrices Server完成数据采集,然后将采集到的数据通过API(Aggregated API,汇总API),例如metrics.k8s.io、custom.metrics.k8s.io、external.metrics.k8s.io,然后再把数据提供给HPA控制器进行查询,以实现基于某个资源利用率对pod进行扩缩容的目的。
  控制管理器默认每隔15s(可以通过–horizontal-pod-autoscaler-sync-period修改)查询metrics的资源使用情况
支持以下三种metrics指标类型:
  ->预定义metrics(比如Pod的CPU)以利用率的方式计算
  ->自定义的Pod metrics,以原始值(raw value)的方式计算
  ->自定义的object metrics
支持两种metrics查询方式:
  ->Heapster
  ->自定义的REST API
支持多metrics

  查看Metrices Server的pod

root@DockerUbuntu18:~# kubectl get pod -A | grep metrics-server
kube-system   metrics-server-85c7b8c8c4-kdln2               1/1     Running   3          6d19h

  查看可以取到每个node的运行状态的数值指标

root@DockerUbuntu18:~# kubectl top nodes
NAME           CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%   
172.18.32.18   230m         12%    968Mi           85%       
172.18.32.19   204m         11%    753Mi           84%       
172.18.32.21   129m         7%     479Mi           182%      
172.18.32.22   164m         9%     437Mi           166%      
172.18.32.23   87m          4%     955Mi           75%       
172.18.32.24   81m          4%     947Mi           74% 

  使用命令创建hpa,并验证hpa资源。

root@DockerUbuntu18:~# kubectl autoscale deployment app1-tomcat-deployment --min=3 --max=5 --cpu-percent=60 -n app1
horizontalpodautoscaler.autoscaling/app1-tomcat-deployment autoscaled
root@DockerUbuntu18:~# kubectl get hpa -n app1
NAME                     REFERENCE                           TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
app1-tomcat-deployment   Deployment/app1-tomcat-deployment   <unknown>/60%   3         5         0          9s
root@DockerUbuntu18:~# kubectl get pod -n app1
NAME                                      READY   STATUS    RESTARTS   AGE
app1-nginx-deployment-7f74d4948d-78lvc    1/1     Running   4          8d
app1-tomcat-deployment-7975ff95c8-h7mb4   1/1     Running   0          5h10m
app1-tomcat-deployment-7975ff95c8-jjsgw   1/1     Running   0          5h11m
app1-tomcat-deployment-7975ff95c8-vlwbk   1/1     Running   0          10s
root@DockerUbuntu18:~# kubectl get hpa -n app1
NAME                     REFERENCE                           TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
app1-tomcat-deployment   Deployment/app1-tomcat-deployment   0%/60%    3         5         3          10m

  这个target可能会等会才会显示,第一次查看的时候显示的unknown,之后再次查看就显示出来了,属于正常情况。而且可以看到tomcat的pod的数量也自动变为了最小值3个。

以yaml文件方式定义hpa

不过通常我们都是使用文件的方式来创建hpa,配置文件如下。


(未完待续)

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐