本地搭建了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进行负载均衡,该实现有待研究。
此次部署的应用架构为:mysql+redis+springcloud+vue。
我们都知道k8s的最小调度单位是pod,所以应用中的每个服务也是以pod为单位来进行管理。每种pod都虚拟化出一层service,集群内部通过访问servicename来访问不同的服务。对于压力比较大的springcloud服务,会进行横向扩容,会有多个实例,也就是有多个pod,当前端访问后端接口时,k8s会通过springcloud 的service 来对多个pod进行负载均衡。多个springcloud服务要分多个deployment和service,之前的想法是把多个服务分别作为多个容器放在1个pod中,这是一个严重跑偏的思路。外部服务发现采用ingress-nginx的方案来实现。
k8s 的隔离机制是使用命名空间进行隔离,不同的命名空间网络是不通的。创建一个命名空间,把应用相关的所有资源放在一个命名空间下。
#Namespace
apiVersion: v1
kind: Namespace
metadata:
name: springcloud-namespace
labels:
name: springcloud-namespace
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次都失败认为真的失败,不健康了,放弃检查。当健康检查失败,容器会在当前节点重启。
#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;
#mysql密码
apiVersion: v1
kind: Secret
metadata:
name: mysql-pwd
namespace: springcloud-namespace
data:
mysql-root-pwd: bXlzcWxwd2Q=
密码部分建议直接使用命令,不要保存在yaml文件中。
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
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
#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
此次实践的重点是k8s的部署,所以只选取了1个spring cloud服务进行部署。目前对微服务不是很懂,应该也没有涉及注册中心等内容。
首先看下项目的配置文件:
#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 .
#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上的)。
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
目标端口是容器的端口,容器的端口是服务的端口。
基础镜像选择的是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 .
#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
#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
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。
去到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
所有的资源服务都已经启动完成
访问浏览器
进行登录操作,发现提示未登录请登录
查看接口地址
接口地址正确
查看接口日志
[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 : 你还没有登录,请先登录!
说明服务调用还是正确的。日志也看不出来是什么问题。问题可能出在微服务应用上。
此次k8s的部署基本成功,但仍然有问题和不完美的地方。
从服务的状态来看状态正常且通过健康检查,有问题的话pod状态会异常。外部请求—>ingress—>前端—>后端—>数据库,从日志看请求也正常的到达后端,这个过程的访问是没有问题的。目前对springcloud不了解,只简单的学习过springboot(部署过一个简单的springboot应用没有出现问题),出现问题没有办法定位。目前怀疑还是springcloud的问题,之后学习下这方面的知识才好处理。
对于k8s的部署,不完美的地方在于mysql的高可用方案。并不是纠结于mysql一定要部署在k8s集群中,大多数应用的mysql都不用k8s部署,主要这其中涉及了共享存储和无头服务的应用,是难点也是k8s的特色,是一个比较好的实践机会。
接触k8s时间不久,初次实现应用的k8s部署,有不正确的地方还请指正!