Kubernetes实践:使用k8s部署微服务应用

目录

  • 1 准备工作
    • 1.1 集群环境说明
    • 1.2 部署方案
  • 2 配置文件
    • 2.1 命名空间
    • 2.2 Mysql
      • 2.2.1 deployment
      • 2.2.2 ConfigMap
      • 2.2.3 Secret
      • 2.2.4 Service
    • 2.3 Redis
      • 2.3.1 deployment
      • 2.3.2 Service
    • 2.4 SpringCloud
      • 2.4.1 image
      • 2.4.2 deployment
      • 2.4.3 Service
    • 2.5 Vue
      • 2.5.1 image
      • 2.5.2 deployment
      • 2.5.3 Service
    • 2.6 Ingress
  • 3 部署
  • 4 总结

1 准备工作

1.1 集群环境说明

本地搭建了3台centOS系统的虚拟机,2台作为master节点,1台作为worker节点。其中m3为worker节点(原计划搭建3master节点的高可用集群,但限制于本地环境资源)

系统类型 IP地址 节点角色 CPU Memory Hostname
centos-7.6 192.168.199.111 master 2核 2G m1
centos-7.6 192.168.199.107 master 2核 2G m2
centos-7.6 192.168.199.174 worker 2核 2G m3

集群环境的具体搭建步骤在此就不赘述了。k8s的搭建方式有2种,一种是使用二进制文件安装,一种是使用kubeadm安装。2种安装方式各有优劣,kubeadm安装比较简单,适合初学者,此次实践使用该安装方式。
特别说明,当前集群基本实现了k8s的高可用,使用keepalived来防止单点故障,设置虚IP192.168.199.155,当m1宕机后,虚IP会漂移到m2,继续提供服务。更进一步的实现方式是使用haproxy对2台master进行负载均衡,该实现有待研究。

1.2 部署方案

Kubernetes实践:使用k8s部署微服务应用_第1张图片
此次部署的应用架构为:mysql+redis+springcloud+vue。
我们都知道k8s的最小调度单位是pod,所以应用中的每个服务也是以pod为单位来进行管理。每种pod都虚拟化出一层service,集群内部通过访问servicename来访问不同的服务。对于压力比较大的springcloud服务,会进行横向扩容,会有多个实例,也就是有多个pod,当前端访问后端接口时,k8s会通过springcloud 的service 来对多个pod进行负载均衡。多个springcloud服务要分多个deployment和service,之前的想法是把多个服务分别作为多个容器放在1个pod中,这是一个严重跑偏的思路。外部服务发现采用ingress-nginx的方案来实现。

2 配置文件

2.1 命名空间

k8s 的隔离机制是使用命名空间进行隔离,不同的命名空间网络是不通的。创建一个命名空间,把应用相关的所有资源放在一个命名空间下。

#Namespace
apiVersion: v1
kind: Namespace
metadata:
   name: springcloud-namespace
   labels:
     name: springcloud-namespace

2.2 Mysql

2.2.1 deployment

mysql的版本选择mysql5.7,使用官方镜像。

containers:
- name: mysql
  image: mysql:5.7
  imagePullPolicy: IfNotPresent
  ports:
  - containerPort: 3306
  args:
    - "--collation-server=utf8mb4_unicode_ci"
    - "--character-set-server=utf8mb4"     
    - "--init-connect='SET NAMES UTF8MB4'"
  env:
  - name: MYSQL_DATABASE
    value: "parking"
  - name: MYSQL_ROOT_PASSWORD
    value: "mysqlpw"

镜像的拉取策略是当本地不存在时拉取镜像,本地存在则使用本地镜像。容器端口使用3306.设置2个环境变量,一个是容器启动时创建一个名字叫parking的库,另一个是root用户的密码。此处密码为明文,后面会实现如何对该密码进行加密。


初始化容器

initContainers:
- name: mysql-init
  image: busybox
  imagePullPolicy: IfNotPresent
  command:  
    - sh
    - "-c"
    - |
      set -ex
      rm -fr /var/lib/mysql/lost+found
  volumeMounts:
    - name: mysql-initdb
      mountPath: /docker-entrypoint-initdb.d

参考其他k8s部署文章,这个容器的作用是对mysql的数据做初始化操作。initContainers的作用是在containers启动之前进行启动,初始化容器和主容器挂载到宿主机的同一个目录下,当mysql启动时,/docker-entrypoint-initdb.d 目录下就会有初始化的sql文件(具体查看下面的configmap)。查看mysql5.7官方镜像的Dockerfile,我们会知道mysql启动时会执行/docker-entrypoint-initdb.d 目录下的sql脚本,则可以完成mysql的初始化操作。


挂载

   volumeMounts:
   - name: mysqldata
     mountPath: /var/lib/mysql 
   - name: mysql-conf
     mountPath: /etc/mysql/conf.d/
   - name: mysql-initdb
     mountPath: /docker-entrypoint-initdb.d
 volumes:
 - name: mysqldata
   hostPath:
     path: /data/mysql_springcloud
 - name: mysql-conf
   configMap:
     name: mysql-config
 - name: mysql-initdb
   #emptyDir: {}
   configMap:
     name: mysql-initsql

mysql的挂载主要是三部分内容,初始化脚本、配置文件和数据文件。初始化脚本和配置文件都是用ConfigMap来管理的,下面具体会有。
数据文件的挂载采用的是挂载在宿主机的目录下,这个挂载方式是有问题的,在生产环境中绝对不能这样实现。因为pod调度在哪台服务器上不是不变的,虽然我们可以使用亲和性来控制pod的调度,但是当mysql所在节点宕机,pod会被调度到另外的机器上,此时就会数据丢失,并且这样也没有实现mysql的高可用。
正确的部署方式是使用共享存储(PV、PVC),如果想要实现高可用,则需要使用StatefulSet,即有状态的服务。因为对于Deployment来说,其所管理的Pod的IP、名字,启停顺序等都是随机的,但是mysql如果要实现主从,那么pod一定是要区分的。这部分的实现之后研究。官方社区给出一个实现方式,目前看来较复杂,可以参考。


健康检查

livenessProbe:
  exec:
    command:
    - /bin/sh
    - "-c"
    - MYSQL_PWD="${MYSQL_ROOT_PASSWORD}"
    - mysql -h 127.0.0.1 -u root -e "SELECT 1"
  initialDelaySeconds: 30                            
  periodSeconds: 20                                   
  timeoutSeconds: 5                                   
  successThreshold: 1                                 
  failureThreshold: 3                                 
readinessProbe:                                       
  exec:
    command:
    - /bin/sh
    - "-c"
    - MYSQL_PWD="${MYSQL_ROOT_PASSWORD}"
    - mysql -h 127.0.0.1 -u root -e "SELECT 1"
  initialDelaySeconds: 30                             
  periodSeconds: 20                                  
  timeoutSeconds: 5                                  
  successThreshold: 1                                 
  failureThreshold: 3   

存活检查和就绪检查都是去查询来查看mysql的服务是否可用。容器启动30秒后进行健康检查,健康检查间隔20秒,健康检查命令超时时间5秒。从错误到正确,只需成功一次,就认为是健康的。健康检查的命令执行3次都失败认为真的失败,不健康了,放弃检查。当健康检查失败,容器会在当前节点重启。

2.2.2 ConfigMap

#mysql 配置文件
apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
  namespace: springcloud-namespace
data:
  my.cnf: |
    [mysqld]   
    skip_external_locking
    lower_case_table_names=1
    skip_host_cache
    skip_name_resolve
    character-set-server = utf8mb4
    collation-server = utf8mb4_general_ci
    init_connect='SET NAMES utf8mb4'
    default-storage-engine=INNODB
#mysql 初始化脚本
apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-initsql
  namespace: springcloud-namespace
data:
  execute.sql: |-
    create database testappdb default character set utf8;

2.2.3 Secret

#mysql密码
apiVersion: v1
kind: Secret
metadata:
  name: mysql-pwd
  namespace: springcloud-namespace
data:
  mysql-root-pwd: bXlzcWxwd2Q=

密码部分建议直接使用命令,不要保存在yaml文件中。

2.2.4 Service

apiVersion: v1
kind: Service
metadata:
  name: mysql-svc
  namespace: springcloud-namespace
  labels:
    name: mysql-svc
spec:
  type: ClusterIP
  ports:
  - port: 3306
    protocol: TCP
    targetPort: 3306
  selector:
    app: mysql

2.3 Redis

2.3.1 deployment

redis 使用了最新的官方镜像

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-deploy
  namespace: springcloud-namespace
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: redis-demo
    spec:
      containers:
      - name: redis-container
        image: redis
        imagePullPolicy: IfNotPresent                           
        ports:
        - containerPort: 6379

2.3.2 Service

#redis service
apiVersion: v1
kind: Service
metadata:
  name: redis-service
  namespace: springcloud-namespace
spec:
  ports:
  - port: 6379
    protocol: TCP
    targetPort: 6379
  selector:
    app: redis-demo
  type: ClusterIP

2.4 SpringCloud

此次实践的重点是k8s的部署,所以只选取了1个spring cloud服务进行部署。目前对微服务不是很懂,应该也没有涉及注册中心等内容。

2.4.1 image

首先看下项目的配置文件:

#mysql
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://${MYSQL_IP}:${MYSQL_PORT}/parking?characterEncoding=utf8&pinGlobalTxToPhysicalConnection=true
spring.datasource.username=${MYSQL_USERNAME}
spring.datasource.password=${MYSQL_PASSWORD}
spring.datasource.dbtype=mysql
#redis
spring.redis.pool.maxTotal=1024
spring.redis.pool.maxIdle=200
spring.redis.pool.maxWaitMillis=1000
spring.redis.pool.testOnBorrow=true
spring.redis.pool.testOnReturn=true
spring.redis.host=${REDIS}
spring.redis.port=6379
spring.cache.cache-names=cache1,cache2
spring.cache.redis.time-to-live=600000

mysql和redis的连接信息写成从环境变量中取

#Dockerfile
FROM openjdk:8 

COPY parking-base.jar /parking-base.jar

CMD ["java","-jar","/parking-base.jar"]

构建镜像

docker build -t  parking-base:v1 .

2.4.2 deployment

#springcloud deploy
apiVersion: apps/v1
kind: Deployment
metadata:
  name: springcloud-deploy
  namespace: springcloud-namespace
spec:
  strategy:
    rollingUpdate:
      maxSurge: 50%        
      maxUnavailable: 50%   
    type: RollingUpdate
  selector:
    matchLabels:
      app: springcloud-demo
  replicas: 1
  template:
    metadata:
      labels:
        app: springcloud-demo
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 1
            preference:
              matchExpressions:
              - key: node
                operator: In
                values:
                - m2
      containers:
      - name: springcloud-demo-container
        image: parking-base:v1
        imagePullPolicy: IfNotPresent
        livenessProbe:
          exec:
            command:
            - /bin/sh
            - "-c"
            - ps -ef|grep java|grep -v grep
          initialDelaySeconds: 30                            
          periodSeconds: 20                                   
          timeoutSeconds: 5                                   
          successThreshold: 1                                 
          failureThreshold: 3                                 
        readinessProbe:                                       
          exec:
            command:
            - /bin/sh
            - "-c"
            - ps -ef|grep java|grep -v grep
          initialDelaySeconds: 30                             
          periodSeconds: 20                                  
          timeoutSeconds: 5                                  
          successThreshold: 1                                 
          failureThreshold: 3   
        ports:
        - containerPort: 9010
        env:
        - name: MYSQL_USERNAME
          value: "root"
        - name: MYSQL_PASSWORD
          value: "mysqlpw"
        - name: MYSQL_IP
          value: "mysql-svc"
        - name: MYSQL_PORT
          value: "3306"
        - name: REDIS
          value: "redis-service"

1.环境变量:末尾的环境变量是mysql用户、mysql密码、mysql ip、mysql端口和reids ip,分别与上面的配置文件对应
2.健康检查:检查容器中的java进程是否存活,如果java进程死掉了,就重启容器
3.更新机制:滚动更新,maxSurge: 50%,最大可超出实例数的百分比。假设2个实例,每次最多多启动1个实例。maxUnavailable: 50%,可容忍不可用实例数的百分比。假设2个实例,允许有一个不可用。
4.亲和性:在之前的部署中,由于m3这个worker节点的资源有限,而k8s默认会把服务优先部署在worker节点,导致部署后访问接口pod奔溃。此处使接口服务更加亲和m2这个master节点,部署时k8s会把这个pod调度到m2上(此处需要保证m2作为master节点具有和worker节点一样的能力,默认pod是不能被调度到master上的)。

2.4.3 Service

apiVersion: v1
kind: Service
metadata:
  name: springcloud-service
  namespace: springcloud-namespace
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 9010
  selector:
    app: springcloud-demo
  type: ClusterIP

目标端口是容器的端口,容器的端口是服务的端口。

2.5 Vue

2.5.1 image

基础镜像选择的是nginx官方的最新镜像

#Dockerfile
FROM nginx

COPY parking  /usr/share/nginx/html/parking

COPY default.conf  /etc/nginx/conf.d/

COPY nginx.conf  /etc/nginx/
#default.conf
server {
	listen 80;
	server_name springcloud.demo.com;

    	location / {
          root   /usr/share/nginx/html/;
          index  index.html index.htm;
    	}	
	location  ~ ^/(base|parkingpay|sys|core|manage|manage|cloud|direct|disperse|monitor|supervise|support)/v1 {
       		proxy_pass http://springcloud-service;
       		proxy_http_version 1.1;
       		proxy_set_header X-Real-IP $remote_addr;
       		proxy_set_header Host $host;
       		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       		proxy_redirect   off;
       		client_max_body_size 100m;
       		expires -1;
       		break;
	}
}

proxy_pass 的地址是后端接口Service的service name
镜像构建

docker build -t parking-ui:v1 .

2.5.2 deployment

#nginx deploy
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
  namespace: springcloud-namespace
spec:
  strategy:
    rollingUpdate:
      maxSurge: 50%               
      maxUnavailable: 50%         
    type: RollingUpdate
  selector:
    matchLabels:
      app: nginx-demo
  replicas: 1
  template:
    metadata:
      labels:
        app: nginx-demo
    spec:
      containers:
      - name: nginx-container
        image: parking-ui:v1
        imagePullPolicy: IfNotPresent                           
        ports:
        - containerPort: 80

2.5.3 Service

#nginx service
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  namespace: springcloud-namespace
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx-demo
  type: ClusterIP

2.6 Ingress

controller使用的是ingress-nginx,由于yaml太长此处就不贴了,具体可以查看官网。

#ingress
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/affinity: cookie
    nginx.ingress.kubernetes.io/session-cookie-hash: sha1
    nginx.ingress.kubernetes.io/session-cookie-name: route
  name: springcloud-ingress
  namespace: springcloud-namespace
spec:
  rules:
  - host: springcloud.demo.com
    http:
      paths:
      - path: /
        backend:
          serviceName: nginx-service
          servicePort: 80

此处我们设置一个域名为 springcloud.demo.com,在本地的hosts文件中配置映射关系,调用的服务是nginx的service name。

3 部署

去到master节点执行命令

[root@m3 parking]# kubectl apply -f parking.yaml 
namespace/springcloud-namespace created
configmap/mysql-config created
configmap/mysql-initsql created
secret/mysql-pwd created
deployment.apps/mysql-deployment created
service/mysql-svc created
deployment.apps/redis-deploy created
service/redis-service created
deployment.apps/springcloud-deploy created
service/springcloud-service created
deployment.apps/nginx-deploy created
service/nginx-service created
ingress.extensions/springcloud-demo-ingress created

查看部署情况

[root@m3 parking]# kubectl apply -f parking.yaml 
[root@m3 parking]# kubectl get all -o wide -n springcloud-namespace
NAME                                     READY   STATUS    RESTARTS   AGE   IP             NODE   NOMINATED NODE   READINESS GATES
pod/mysql-deployment-5486d788cf-9crsk    1/1     Running   0          83m   172.22.2.215   m3                
pod/nginx-deploy-766c7dc559-4jx98        1/1     Running   0          83m   172.22.2.217   m3                
pod/redis-deploy-54b57b76bf-8m4px        1/1     Running   0          83m   172.22.2.216   m3                
pod/springcloud-deploy-b9cccd6c7-7qqbv   1/1     Running   0          83m   172.22.1.175   m2                

NAME                          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE   SELECTOR
service/mysql-svc             ClusterIP   10.109.40.228           3306/TCP   83m   app=mysql
service/nginx-service         ClusterIP   10.104.81.76            80/TCP     83m   app=nginx-demo
service/redis-service         ClusterIP   10.102.23.193           6379/TCP   83m   app=redis-demo
service/springcloud-service   ClusterIP   10.97.214.89            80/TCP     83m   app=springcloud-demo

NAME                                 READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS                   IMAGES                                          SELECTOR
deployment.apps/mysql-deployment     1/1     1            1           83m   mysql                        mysql:5.7                                       app=mysql
deployment.apps/nginx-deploy         1/1     1            1           83m   nginx-container              harbor.guojiaxing.red/private/parkingui:v1      app=nginx-demo
deployment.apps/redis-deploy         1/1     1            1           83m   redis-container              redis                                           app=redis-demo
deployment.apps/springcloud-deploy   1/1     1            1           83m   springcloud-demo-container   harbor.guojiaxing.red/private/parking-base:v3   app=springcloud-demo

NAME                                           DESIRED   CURRENT   READY   AGE   CONTAINERS                   IMAGES                                          SELECTOR
replicaset.apps/mysql-deployment-5486d788cf    1         1         1       83m   mysql                        mysql:5.7                                       app=mysql,pod-template-hash=5486d788cf
replicaset.apps/nginx-deploy-766c7dc559        1         1         1       83m   nginx-container              harbor.guojiaxing.red/private/parkingui:v1      app=nginx-demo,pod-template-hash=766c7dc559
replicaset.apps/redis-deploy-54b57b76bf        1         1         1       83m   redis-container              redis                                           app=redis-demo,pod-template-hash=54b57b76bf
replicaset.apps/springcloud-deploy-b9cccd6c7   1         1         1       83m   springcloud-demo-container   harbor.guojiaxing.red/private/parking-base:v3   app=springcloud-demo,pod-template-hash=b9cccd6c7

所有的资源服务都已经启动完成

访问浏览器
Kubernetes实践:使用k8s部署微服务应用_第2张图片
进行登录操作,发现提示未登录请登录
查看接口地址
Kubernetes实践:使用k8s部署微服务应用_第3张图片
接口地址正确
查看接口日志

[root@m3 parking]# kubectl logs -f springcloud-deploy-b9cccd6c7-7qqbv -n springcloud-namespace
2019-08-03 09:11:31.003 DEBUG [-,625a956d1361ae48,625a956d1361ae48,true] 1 --- [nio-9010-exec-7] com..config.CorsFilter    : *****************进入全局过滤器,拦截所有请求******************
2019-08-03 09:11:31.026 DEBUG [-,625a956d1361ae48,625a956d1361ae48,true] 1 --- [nio-9010-exec-7] c.c.c.c.o.service.SecurityServiceImpl    : 你的当前请求地址://logon
2019-08-03 09:11:31.026  INFO [-,625a956d1361ae48,625a956d1361ae48,true] 1 --- [nio-9010-exec-7] c.c.c.c.o.service.SecurityServiceImpl    : 你还没有登录,请先登录!

说明服务调用还是正确的。日志也看不出来是什么问题。问题可能出在微服务应用上。

4 总结

此次k8s的部署基本成功,但仍然有问题和不完美的地方。
从服务的状态来看状态正常且通过健康检查,有问题的话pod状态会异常。外部请求—>ingress—>前端—>后端—>数据库,从日志看请求也正常的到达后端,这个过程的访问是没有问题的。目前对springcloud不了解,只简单的学习过springboot(部署过一个简单的springboot应用没有出现问题),出现问题没有办法定位。目前怀疑还是springcloud的问题,之后学习下这方面的知识才好处理。
对于k8s的部署,不完美的地方在于mysql的高可用方案。并不是纠结于mysql一定要部署在k8s集群中,大多数应用的mysql都不用k8s部署,主要这其中涉及了共享存储和无头服务的应用,是难点也是k8s的特色,是一个比较好的实践机会。
接触k8s时间不久,初次实现应用的k8s部署,有不正确的地方还请指正!

你可能感兴趣的:(应该是个假运维)