spring-cloud on kubernetes 实践
spring-cloud on kubernetes 实践原有运维系统缺点原有业务布署在虚拟机ecs kvm ,脚本分散,日志分散难于集中收集管理,监控无法统一,cpu、内存、磁盘资源得用率低,运维效率极低,无法集中管理新业务布署需要开通新的虚拟机,需要单独定制监控,各种crontab ,配置脚本,效率低下,ci-cd jenkins配置繁琐k8s容器化优势利用k8s容器平台nam...
spring-cloud kubernetes 实践
随着公司业务量和产品线的增加,项目越来越多,普通运维系统架构对整个软件研发生命周期的管理越来越难,效率低下,难以统一管理。近年来docker统一了容器标准,对于软件开发流程产生了深远的影响,docker可以一次打包,处处运行。过去几年Kubernetes平台发展日新月益,Kubernetes统一了容器排编王者的地位,我个人认为kubernetes可以说是对普通运维架构一次突破性的革命。利用kubrenets集群平台可以很方便的对容器服务进行集中管理,可以非常高效的对容器服务进行编排、调度、扩容、升级、回滚、监控、集中收集日志等,基本上把传统运维架构需要考虑的问题全部解决了,而devops容器化也是整个软件开发流程的必经之路,因此我们对现有老旧的运维平台进行替换,统一利用kubernetes对所有业务进行管理。
原有运维系统缺点
- 原有业务布署在虚拟机ecs kvm ,脚本分散,日志分散难于集中收集管理,监控无法统一,cpu、内存、磁盘资源得用率低,运维效率极低,无法集中管理。
- 新业务布署需要开通新的虚拟机,需要单独定制监控,各种crontab ,配置脚本,效率低下,ci-cd jenkins配置繁琐。
k8s容器化优势
- 利用k8s容器平台namespaces对不同环境进行区分,建产不同dev、test 、stage、prod环境,实现隔离。
- 通过容器化集中布署所有业务,实现一键布署所需环境业务。
- 统一集中监控报警所有容器服务异常状态。
- 统一集中收集所有服务日志至elk集群, 利用kibana面板进行分类,方便开发查日志。
- 基于k8s命令行二次开发,相关开发、测试人员、直接操作容器。
- 基于rbac对不同的环境授于不同的开发、测试访问k8s权限,防止越权。
- 通过jenkins 统一ci-cd编译发布过程。
- 项目容器化后, 整体服务器cpu、 内存、磁盘、资源利用减少%50,运维效率提高%60,原来需要N个运维做的事,现在一个人即可搞定。
k8s本身是一套分布式系统,要用好会遇到很多问题,不是说三天两头就能搞定,需要具备网络、linux系统、存储,等各方面专业知识,在使用过程中我们也踩了不少坑, 我们是基于二进制方试安装,我们k8s版本为1.10,经过一段时间的实践,k8s对于我们整个开发、测试、发布、运维流程帮助非常大,值得大力推广。
网络方案选择
- flanneld vxlan udp以及 hsot-gw 所有节点同步路由 ,使用简单,方便,稳定,k8s入门首选。
- calico 基于BGP协议的路由方案,支持acl ,部署复杂,出现问题难排查。
- Weave UDP广播,本机建立新的BR,通过PCAP互通 ,国内使用比较少。
- Open vSwitch UDP广播,本机建立新的BR,通过PCAP互通,openshift 以及混合云使用比较多。
我们对各个网络组件进行过调研对比,网络方案选择的是flanneld-hostgw+ipvs,在k8s1.9之前是不支持ipvs,kube-proxy负责所有svc规则的同步,使用的iptables,一个service会产生n条iptables记录。如果svc增加到上万条,iptables-svc同步会很慢,得几分钟,使用ipvs之后,所有节点的svc由ipvs lvs来负载,更快,更稳定。而且简单方便,使用门槛低, host-gw会在所有节同步路由表,每个容器都分配了一个IP地址,可用于与同一主机上的其他容器进行通信。对于通过网络进行通信,容器与主机的IP地址绑定。flanneld-hostgw性能接近calico,相对来说falnneld配置布署比calico简单很多。顺便提下flanneld-vxlan这种方式,需要通过udp封包解包,效率较低,适用于一些私有云对网络封包有限制,禁止路由路由表添加等有限制的平台。
flanneld 通过为每个容器提供可用于容器到容器通信的IP来解决问题。它使用数据包封装来创建跨越整个群集的虚拟覆盖网络。更具体地说,flanneld为每个主机提供一个IP子网(默认为/ 24),Docker守护程序可以从中为每个主机分配IP。
flannel使用etcd来存储虚拟IP和主机地址之间的映射。一个flanneld守护进程在每台主机上运行,并负责维护ETCD信息和路由数据包。
在此提一下,在使用flannled使用过程中遇到过严重bug 即租约失效,flanneld会shutdown 节点 网络组件,节点网络直接崩掉,解决办法是设置永久租期:https://coreos.com/flannel/docs/latest/reservations.html#reservations
传统业务迁移至k8s遇到的问题和痛点,devops遇到问题?
使用k8s会建立两套网络,服务之间调用通过svc域名,默认网络、域名和现有物理网络是隔离的,开发,测试,运维无法像以前一样使用虚拟机一样,postman ip+端口 调试服务, 网络都不通,这些都是问题。
- pod网络 和物理网络不通,windows办公电脑、linux虚拟机上现有的业务和k8s是隔离的。
- svc网络 和物理网络不通,windows办公电脑、linux虚拟机上现有的业务和k8s是隔离的。
- svc域名和物理网络不通,windows办公电脑、linux虚拟机上现有的业务和k8s是隔离的。
- 原有nginx 配置太多的location 几百层,不好迁移到ingress-nginx,ingress只支持简单的规则。
- svc-nodeport访问,在所有node上开启端口监听,占用node节点端口资源,需要记住端口号。
- ingress http 80端口, 必需通过域名引入,ingress http 80端口必需通过域名引入,原来简单nginx的location可以通过ingress引入。
- tcp–udp–ingress tcp udp 端口访问需要配置一个ingress lb,很麻烦,要先规划好lb节点同样也需要仿问lb端口。
- 原有业务不能停,继续运行,同时要能兼容k8s环境,和k8s集群内服务互相通讯调用,网络需要通。
传统虚拟机架构我们只需要一个地址+端口直接访问调试各种服务,k8s是否能做到不用改变用户使用习惯,无感知使用呢?答案是打通devops全链路,像虚拟机一样访部k8s集群服务 , 我们打通k8s网络和物理网理直通,物理网络的dns域名直接调用k8s-dns域名服务直接互访,所有服务互通。公司原有业务和现有k8s集群无障碍互访。
配置一台k8s node节点机做路由转发,配置不需要太高,布署成路由器模式,所有外部访问k8s集群流量经该节点, 本机ip: 192.168.2.71 。
vim /etc/sysctl.conf
net.ipv4.ip_forward = 1
设置全网路由通告,交换机或者linux、windows主机加上静态路由,打通网络。
route add -net 172.20.0.0 netmask 255.255.0.0 gw 192.168.2.71
route add -net 172.21.0.0 netmask 255.255.0.0 gw 192.168.2.71
增加dns服务器代理,外部服务需要访问k8s svc域名,首先需要解析域名,k8s服务只对集群内部开放,此时需要外部要能调用kube-dns 53号端口,所有办公电脑,业务都来请求kube-dns肯定撑不住 ,实时上确实是撑不住,我们做过测试,此时需要配置不同的域名进行分流策略,公网域名走公网dns,内部.svc.cluster.local走kube-dns。
- 建立dns代理服务器,ingress建立一个nginx-ingress服务反代kube-dns,ingress-nginx绑定到dns节点运行,在节点上监听 dns 53 端口。
[root@master1 kube-dns-proxy-1.10]# cat tcp-services-configmap.yaml
kind: ConfigMap
apiVersion: v1
metadata:
name: tcp-services
namespace: ingress-nginx
data:
53: "kube-system/kube-dns:53"
[root@master1 kube-dns-proxy-1.10]# cat udp-services-configmap.yaml
kind: ConfigMap
apiVersion: v1
metadata:
name: udp-services
namespace: ingress-nginx
data:
53: "kube-system/kube-dns:53"
[root@master1 kube-dns-proxy-1.10]# cat ingress-nginx-deploy.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-ingress-controller-dns
namespace: ingress-nginx
spec:
replicas: 1
selector:
matchLabels:
app: ingress-nginx-dns
template:
metadata:
labels:
app: ingress-nginx-dns
annotations:
prometheus.io/port: '10254'
prometheus.io/scrape: 'true'
spec:
hostNetwork: true
serviceAccountName: nginx-ingress-serviceaccount
containers:
- name: nginx-ingress-controller-dns
image: registry-k8s.novalocal/public/nginx-ingress-controller:0.12.0
args:
- /nginx-ingress-controller
- --default-backend-service=$(POD_NAMESPACE)/default-http-backend
# - --configmap=$(POD_NAMESPACE)/nginx-configuration
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
- --annotations-prefix=nginx.ingress.kubernetes.io
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: http
containerPort: 80
#- name: https
# containerPort: 443
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
nodeSelector:
node: dns
- 最简单快捷的方式是安装dnsmasq,当然你也可以用bind ,powerdns,croedns等改造,上游dns配置为上一步骤增加nginx-ingress dns的地址,所有办公,业务电脑全部设置dns为此机,dnsmasq.conf 配置分流策略
no-resolv
server=/local/192.168.1.97
server=114.114.114.114
完成以上步骤 k8s podip svcip svc域名和办公,现有ecs、虚拟机完美融合,无缝访问,容器网络问题搞定。
windows访问k8s svc畅通无组,开发测试,完美无缝对接。
ingress-nginx 服务入口接入
服务发布后最终对接的是用户,用户访问k8s服务需要通过nginx或其它http服务器接入,对于服务接入我们同时使用两种不同的方案,取决于nginx location 复杂度,location规则简单的我们使用第一种方案,由于各种问题,location复杂我们使用第二种方案。
-
client-------ingress-nginx-----upstream----podip,对于ingress-nginx官方使用的原始方案,先配置ingress规则路由,ingress对接不同的svc-dns域名,ingress自动发现后端 podip ,通过upstream 负载不同的后端podip ,不同的域名路由到不同的k8s后端podip, 用户客户端访问流量会负载到不同的pod上。
-
client------nginx-------upstream------svc-----podip 改造现有nginx兼容k8s, 对接k8s-svc服务。对于nginx location规则过多,不能很好的兼容nginx-ingress导致使用k8s非常困难,难以普及,在不变更现有nginx配置的情况下如何对接k8s这是一个问题,经过前面网络打通的步骤我们所有网络的问题都已解决。现在只需改动很小部分即可兼容,由于k8s podip是漂移的,ip总是会变的,nginx只能是对接svc域名才能持久,但是nginx解析域名有个bug,只解析一次,如果在此期间删除了yaml,nginx会找不到后端svcip,所以这里要设置代理变量set $backend,设置resolver的dns为代理dns地址,设置解析域名时间和变量解决该问题。
location /tomcat/ {
resolver 192.168.1.97 valid=3600s;
set $backend "tomcat.dac-prod.svc.cluster.local";
error_log logs/dac_error.log error;
access_log logs/dac_access.log main;
proxy_set_header X-real-ip $remote_addr;
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_redirect off;
client_max_body_size 100M;
proxy_pass http://${backend}:9090;
}
大家可能担心eureka和k8s-svc有冲突,spring-cloud本身自带服务发现eureka,组件之间的调用通过eureka注册调用,其实你直接布署就行了,eureka和service没任何冲突,和普通java应用一样用。
监控方案
目前使用的k8s官方的heapster,monitoring-influxdb-grafana +自定议脚本+自定义grafana面板 可以灵活报警
监控面板按业务环境dev/test/stage/prod/对cpu/内存/网络等分类进行展示
节点资源监控
pod cpu、内存,网络等监控
监控脚本,可以很灵活跟据设定参数进行钉钉报警,报告有问题的pod,node,自动处理有问题的服务。
#!/bin/bash
#最大内存排除的node节点
exclude_node="node7|node1|node2|node3|master1"
exclude_pod="redis|kafka|mongo|zookeeper|Evicted|Completed"
#node使用的最大报警内存%比
node_mem_max="100"
#node最大使用cpu百分比
node_cpu_max="80"
#pod使用的最大报警内存MB
pod_mem_max="4096"
pod_top="5"
pod_top_cpu="10"
#pod的启动错误时间,单位为秒s
pod_error_m_time="120"
pyding="$HOME/k8s-dev/dingd-zabbix.python"
#pod的内存以及cpu的使用状态
pod_mem=$(/usr/local/bin/kubectl top pod --all-namespaces |sort -n -k4 )
#node的内存使用状态
node_status=$(/usr/local/bin/kubectl top node|egrep -v "${exclude_node}" |egrep -v "MEMORY%")
#pod的运行状态
pod_status=$(/usr/local/bin/kubectl get pod --all-namespaces -o wide|grep -v NAMESPACE)
#设定有问题的pod存取文件路径
alert_error_pod="/tmp/alert-error-pod.txt"
#设定最大内存占用节点上pod的文件列表路径
alert_list="/tmp/alert-mem-list.txt"
#监控cpu百分比文件输出路径
alert_node_cpu_list="/tmp/alert_node_cpu_list.txt"
#取node内存的百分比数字值
#node_pre_mem=$(echo "${node_mem}"|awk '{print $5}'|sed -e "s/%//g")
#监控node的内存百分比,列出占用内存最高的应用并重启top5应用
node_mem_mon () {
echo "${node_status}" |awk '{print $1,$5}'|sed -e "s/%//g" |while read node_name node_mem_status;do
#echo $node_name $node_mem_status
if [ "${node_mem_status}" -gt "${node_mem_max}" ];then
>${alert_list}
#找到该节点上的所有的pod名
find_pod=$(echo "${pod_status}"|egrep ${node_name}|awk '{print $2}')
#找到所有节点倒排序使用最大的内存的pod列表
for i in $(echo "${find_pod}");do
echo "${pod_mem}"|grep $i >>${alert_list}
done
date_time=`date +'%F-%T'`
echo -e "\n${node_name}最大内存超过 %${node_mem_max} 以下pod应用将被重启 ------------------\n"
cat ${alert_list}|sort -n -k 4|tail -${pod_top}
python ${pyding} "`echo -e "\n ${date_time} ${node_name}当前内存为${node_mem_status}%,最大内存超过 %${node_mem_max} 以下pod应用将被重启 ------------------\n" ;cat ${alert_list}|sort -n -k 4|egrep -v "$exclude_pod"|tail -${pod_top}` "
cat ${alert_list}|sort -n -k 4|egrep -v "$exclude_pod"|tail -${pod_top}|egrep -v "应用将被重启" | awk '{print "/usr/local/bin/kubectl delete pod "$2" -n "$1" " | "/bin/bash"}'
fi
done
}
钉钉报警图
k8s集群 yaml容器编排管理
k8s通过yaml对容器进行管理,yaml配置编排文件是管理整个容器生命周期重要的一部份,管理好yaml非常重要。我开发了一套类似于helm的模板的脚本框架,用于所有环境的yml初始化工作 ,自己写脚本的好处就是可以灵活控制,比如哪个组件要挂载存储,共享卷,要配置私有hosts等,我可以一次性定制好,初始化时只需要init-yml直接批量搞定,不需要每个yml单独去修改,之后就是kubectl create 直接用。
容器编yaml排文件按空间环境dev-test-stage-prod进行模板base分类,复制一套模板即可生它其它各环境, 容器编排按业务类型模块配置conf app-list。
[root@master1 config]# ls
public-dev_app_list.conf public-test-base.yml
public-dev-base.yml sms-test_app_list.conf
public-pretest_app_list.conf sms-test-base.yml
public-pretest-base.yml wbyh-dev_app_list.conf
public-stage_app_list.conf wbyh-dev-base.yml
public-stage-base.yml wbyh-stage_app_list.conf
public-test_app_list.conf wbyh-stage-base.yml
通过k8s核心排编脚本进行init-yml初始化对应环境, 生成所有pod 的yaml排编文件,每套环境可以生成环境对应的 mysql redis kafka mongo等,直接启动即可调用。
[root@master1 k8s-dev]# ./k8s wbyh-stage init-yml
/root/k8s-dev/config
[root@master1 k8s-dev]# tree
wbyh-stage/
├── app
│ ├── dac-api-center
│ │ └── dac-api-center.yml
│ ├── dac-app-web
│ │ └── dac-app-web.yml
│ ├── dac-config-server
│ │ └── dac-config-server.yml
│ ├── dac-eureka-server
│ │ └── dac-eureka-server.yml
│ ├── dac-task
│ │ └── dac-task.yml
│ ├── dac-task-apply
│ │ └── dac-task-apply.yml
│ ├── dac-task-h5
│ │ └── dac-task-h5.yml
│ ├── dac-web
│ │ └── dac-web.yml
│ ├── dac-message-center
│ │ └── dac-message-center.yml
│ ├── dac-quartz-jfdata
│ │ └── dac-quartz-jfdata.yml
│ ├── dac-quartz-mach
│ │ └── dac-quartz-mach.yml
│ ├── dac-quartz-dac
│ │ └── dac-quartz-dac.yml
│ ├── dac-resources-center
│ │ └── dac-resources-center.yml
│ ├── dac-resources-item
│ │ └── dac-resources-item.yml
│ ├── dac-usercenter-web
│ │ └── dac-usercenter-web.yml
│ └── tomcat
│ └── tomcat.yml
└── stateful-sets
├── kafka
│ ├── 10kafka-config-0420yml
│ ├── 10kafka-config.yml
│ ├── 20dns.yml
│ └── 50kafka.yml
├── mongo
│ └── mongo-statefulset.yml
├── redis
│ ├── primary.yml
│ └── redis-configmap.yml
└── zookeeper
├── 10zookeeper-config.yml
├── 30service.yml
└── 50pzoo.yml
22 directories, 26 files
通过k8s 脚本调用kubectl 可以直接批量创建该空间下所有服务
[root@master1 k8s-dev]# ./k8s wbyh-stage create_all
/root/k8s-dev/config
configmap "dac-eureka-server-filebeat-config" created
service "dac-eureka-server" created
deployment.extensions "dac-eureka-server" created
configmap "dac-config-server-filebeat-config" created
service "dac-config-server" created
deployment.extensions "dac-config-server" created
configmap "tomcat-filebeat-config" created
service "tomcat" created
deployment.extensions "tomcat" created
所有代码存入gitlab做版本管理,即基础设施即代码
add svn-jar-version ll item
commit 29dc05530d839c826130eef81541ce96a155107b
Author: idea77 <idea77@qq.com>
Date: Thu Sep 20 16:11:00 2018 +0800
mod ossfs to /Rollback/oss
commit 880bcd9483a6ee1f5ca440fef017b30ba7cd14fe
Author: idea77 <idea77@qq.com>
Date: Wed Sep 19 16:57:43 2018 +0800
存储方案
目前公司一部份用应用挂载的卷为nfs,读写要求不高的可以配置nfs, 一部份要求比较高的用的ceph,如mysql kafka之类的就需要ceph支撑,对于持需要持 久化的db类型存储的管理用storageclass 存储类对接管理,很方便自动建立存储卷pv-pvc对接,共享卷类型可以直接挂载卷。
nfs配置需要在每个node节点安装nfs-utils ,配置yml, 注意centeos7 低版本nfs-server有bug导致服务器重启,升到4以上内核解决问题。
- name: tomcat-img
nfs:
path: /home/k8s-nfs-data/dac-test-tomcat-img
server: 192.168.8.30
ceph k8s node节点安装ceph-common,配置storageclass
ceph-class.yaml
apiVersion: storage.k8s.io/v1beta1
kind: StorageClass
metadata:
name: ceph-db
provisioner: kubernetes.io/rbd
parameters:
monitors: 192.168.1.31:6789
adminId: admin
adminSecretName: ceph-secret
adminSecretNamespace: kube-system
pool: rbd
userId: admin
userSecretName: ceph-secret
jenkins ci-cd编译发布阶段
jenkins -ci-cd 控制台 完成整个jar包编译,dockerfile编译、docker push 、k8s deployment 镜像滚动升级功能。
jenkins Manage and Assign Roles 授权不同的开发、测试组不同的用户权限,隔离不同的项目编译发布权限。
目前没有完全用上流水线服务,完全流水线需要构建不报错,一报错也就无法完成,不是很灵活,构建jar包和发布docker-image是分开的,需要跟据公司业务来。
编译阶段我们做了钉钉通知,每个项目拉了自己的群,编译jar包是否成功整个组都有通知,同样update也是一样发布是不成功都有提示,群内可见。
目前我们k8s容器启动分为两种架构
-
容器发布后启动基础jdk镜相,wget去http服务器下载对应目录编译好的jar包,然后启动,即无镜像模式,适合频繁发布类型的业务,push jar to oss有一部份业务是跑虚拟机,需要jar包,oss可以做共享。
-
容器发布按照标准的方式打image update-imae模式,适合出错及时回滚的业务,即编译dockerfile-push-docke-image-update-deployment
build-$namespace 通过空间变量名拟写对应脚本,基本是做一个通用模板base,复制生成对应项目的build.sh供jenkins传参调用,每套环境有自己的基础镜像base,基础镜像就是打入jdk等一些私有的配置,编译的时候在基础镜像上加上jar包。
if [[ $MY_POD_NAMESPACE =~ -dev ]];then
#定义启动基础镜相
base_image="registry-k8s.novalocal/public/yh-centos7-jdk-1.8"
#定义APP镜像仓库地址
image_path="registry-k8s.novalocal/xl_public/$MY_POD_NAMESPACE/${APP}"
elif [[ $MY_POD_NAMESPACE =~ "-test" ]];then
#定义启动基础镜相
base_image="registry-k8s.novalocal/public/yh-centos7-jdk-1.8"
#定义APP镜像仓库地址
image_path="registry-k8s.novalocal/xl_public/$MY_POD_NAMESPACE/${APP}:${date_time}"
elif [[ $MY_POD_NAMESPACE =~ -stage ]];then
#定义启动基础镜相
base_image="registry-k8s.novalocal/xl_public/wbyh-base/centos7-jdk-1.8"
#定义idc镜相仓库路径
image_path="registry.cn-hangzhou-idc.com/xl_dac/wbyh-stage-${APP}:${date_time}"
vpc_image_path="registry-vpc.cn-hangzhou-idc.com/wbyh-stage-${APP}:${date_time}"
fi
#初始化dockerfile
init_dockerfile () {
#生成Dockerfile
cd /Rollback/build-docker/
echo "" >$MY_POD_NAMESPACE/${APP}/Dockerfile
#生成基础镜像地址
echo -e "${base_image}" >>${MY_POD_NAMESPACE}/${APP}/Dockerfile
#生成docker作者
echo -e "MAINTAINER idea77@qq.com" >>${MY_POD_NAMESPACE}/${APP}/Dockerfile
echo -e "USER root" >>${MY_POD_NAMESPACE}/${APP}/Dockerfile
#获取启动脚本
\cp -f start-sh/${MY_POD_NAMESPACE}-sh/${APP}.sh $MY_POD_NAMESPACE/${APP}/
echo -e "ADD ./${APP}.sh /home/deploy/" >>${MY_POD_NAMESPACE}/${APP}/Dockerfile
#添加 jar包到/home/deploy/
echo -e "${add_jar}" >>${MY_POD_NAMESPACE}/${APP}/Dockerfile
#暴露端口
echo -e "EXPOSE 9090" >>${MY_POD_NAMESPACE}/${APP}/Dockerfile
#添加docker入口启动文件
\cp -f start-sh/templates/docker-entrypoint.sh $MY_POD_NAMESPACE/${APP}/
echo -e "ADD ./docker-entrypoint.sh /docker-entrypoint.sh" >>$MY_POD_NAMESPACE/$APP/Dockerfile
echo -e "RUN chown -R deploy:deploy /home/deploy && chown -R deploy:deploy /docker-entrypoint.sh && ls -t --full /home/deploy " >>$MY_POD_NAMESPACE/$APP/Dockerfile
echo -e "USER deploy" >>$MY_POD_NAMESPACE/$APP/Dockerfile
echo -e 'ENTRYPOINT ["/docker-entrypoint.sh"]' >>$MY_POD_NAMESPACE/$APP/Dockerfile
if [[ ${MY_POD_NAMESPACE} =~ -prod ]];then
docker images |grep xl_prod|grep ${APP}|awk '{print $1":"$2}'|xargs docker rmi -f
else
docker images |grep min-test|grep ${APP}|awk '{print $1":"$2}'|xargs docker rmi -f
fi
name="${MY_POD_NAMESPACE},build ${image_path}-${svn_version}"
cd /Rollback/build-docker/$MY_POD_NAMESPACE/$APP/
docker build --no-cache -t ${image_path}-${svn_version} .
check
if [[ $MY_POD_NAMESPACE =~ -stage ]];then
#vpc专有镜相地址修改到yml文件
sed -i "s@registry-vpc.cn-hangzhou-idc.com/xl_public\(.*\)@${vpc_image_path}-${svn_version}@g" /home/deploy/k8s-dev/${MY_POD_NAMESPACE}/app/$APP/$APP.yml
elif [[ $MY_POD_NAMESPACE =~ -test ]];then
sed -i "s@registry-k8s.novalocal/xl_public/\(.*\)@${image_path}-${svn_version}@g" /home/deploy/k8s-dev/${MY_POD_NAMESPACE}/app/$APP/$APP.yml
fi
name="push ${APP}"
docker push ${image_path}-${svn_version}
check
}
jenkins 触发
build-----push------updae-deployment-----image 整个过程是流水线形式,一次性连续完成,完成后通过机器人通知到各业务组,中间有任何问题,机器人会告诉我们在哪个阶段出错,很方便排查问题,镜像的版本号跟据git或svn的版本号来获取,然后加上当前时间戳,在jar包编译阶段版本号会写入特定文件,jenkins会跟据当前编译的版本生成对应的docker镜像版本。
k8s 日志方案
普通虚拟机日志分散,难管理,需要登陆虚拟机一个个查看,k8s-docker可以很方便帮我们收集管理日志,日志方案有几种。
- 应用打到docker stdout 前台输出,docker输出到/var/lib/containers, 通过filebeat、fluentd 、daemonsets组件收集,这种对于小量日志还可以,大量日志性能很差,写入很慢.
- pod挂载host-path 把日志打到宿主机,宿主机启动filebeat, fluentd 、daemonsets 收集,无法判断来自哪个容器,pod namespaces。
- pod的yml中定义两个 container ,同时启动一个附加的filebeat,两个container挂载一个共享卷来收集日志
我们用的第三种方案,通过一个附加容器filebeat来收集所有日志, filebeat–kakfa–logstash–es,自定义编译filebeat 镜相,为filebeat打上podip空间svc名等标签,方便识别来自哪个容器,哪个namespace,配置config-map以及yml。
- filebeat----kafkacluster-----logstash----es
apiVersion: v1
kind: ConfigMap
metadata:
namespace: dac-prod
name: dac-config-server-filebeat-config
data:
filebeat.yml: |
filebeat.prospectors:
- input_type: log
fields:
namespace: dac-prod
service-name: dac-config-server
#pod-ip:
paths:
- "/mnt/*.log"
multiline:
pattern: '^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3}'
negate: true
match: after
#output.elasticsearch:
output.kafka:
hosts: ["10.31.222.108:9092", "10.31.222.109:9092", "10.31.222.110:9092"]
topic: applog
required_acks: 1
compression: gzip
# Available log levels are: critical, error, warning, info, debug
logging.level: info
---
apiVersion: v1
kind: Service
metadata:
name: dac-config-server
namespace: dac-prod
spec:
ports:
- port: 9090
name: http
selector:
app: dac-config-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: dac-config-server
namespace: dac-prod
labels:
app: dac-config-server
spec:
replicas: 1
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
selector:
matchLabels:
app: dac-config-server
template:
metadata:
labels:
app: dac-config-server
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- dac-config-server
topologyKey: "kubernetes.io/hostname"
imagePullSecrets:
- name: myregistrykey
containers:
- image: registry-vpc.cn-hangzhou-idc.com/dac-prod-dac-config-server:v1
name: dac-config-server
imagePullPolicy: Always
resources:
limits:
cpu: 4000m
memory: 4096Mi
requests:
cpu: 150m
memory: 1024Mi
env:
- name: APP
value: dac-config-server
#public
- name: JAVA_OPTS
value: "-Xms4g -Xmx4g"
- name: CONTAINER_CORE_LIMIT
value: "4"
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: MY_POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
readinessProbe:
tcpSocket:
port: 9090
initialDelaySeconds: 60
timeoutSeconds: 3
livenessProbe:
tcpSocket:
port: 9090
initialDelaySeconds: 60
timeoutSeconds: 3
ports:
- name: http
containerPort: 9090
volumeMounts:
#- name: opt-data
#mountPath: /home/deploy
- name: logs
mountPath: /home/deploy/logs
- name: host-time
mountPath: /etc/localtime
readOnly: true
- image: registry-vpc.cn-hangzhou-idc.com/dac_prod/filebeat:6.0.0
name: filebeat
imagePullPolicy: Always
env:
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
volumeMounts:
- name: logs
mountPath: /mnt
- name: filebeat-conf
mountPath: /etc/filebeat
- name: host-time
mountPath: /etc/localtime
readOnly: true
nodeSelector:
node: public
volumes:
- name: logs
emptyDir: {}
- name: filebeat-conf
configMap:
name: dac-config-server-filebeat-config
#- name: opt-data
#nfs:
#path: /home/k8s-nfs-data/public-dev-base
#server: 10.10.1.30
- name: host-time
hostPath:
path: /etc/localtime
filebeat收集日志打上关键字标签,namespace ,svc,podip 等
kibana 集中日志展示,建立dashboard分类,用户可以按namespce 分类不同环境,过滤选择查看不同模块的应用日志
RBAC+二次开发k8s脚本
简化kubectl 命 令, 提供给研发团队使用。实际上这里功能和jenkins以及kibana上是重复的,但是必需考虑到所有团队成员的使用感受,有人喜欢命令行,有人喜欢界面,简单好用就够。我打个比方,比如看日志,有人可能喜欢用命令行tail -f 看日志,用grep过滤等,有人喜欢用kibana看,那怎么办?于就有了两种方案,喜欢用图形界面的就去jenkins或kibana,你想用命令行的就给你命令行,满足你一切需求。统一集中通过指定的机器提供给开发、测试、运维、使用,方便调试,排障。通过统一的入口可以直接对容器进行服务创建,扩容,重启,登陆,查看日志,查看java启动参数 等,方便整个团队沟通。
在这里我们通过k8s rbac 授权身份认证 生产证书key kube-config key,授于不同项目组不同的管理权限,不同的项目组只有自己项目的权限,权限做了细分,不同研发、测试团队互不干扰。
[deploy@185 app]# k8s dac-test get_all
NAME READY STATUS RESTARTS AGE IP NODE
accountant-3536198527-dtrc9 2/2 Running 0 21h 172.20.1.5 node3.k8s.novalocal
analyzer-1843296997-vz9nc 2/2 Running 0 21h 172.20.87.15 node5.k8s.novalocal
api-1260757537-gxrp2 2/2 Running 0 21h 172.20.71.6 k8s-monitor.novalocal
calculator-1151720239-pr69x 2/2 Running 0 21h 172.20.1.12 node3.k8s.novalocal
consul-0 1/1 Running 0 21h 172.20.87.3 node5.k8s.novalocal
dispatcher-2608806384-kp433 2/2 Running 0 21h 172.20.4.6 lb1.k8s.novalocal
geo-1318383076-c7th2 2/2 Running 0 5m 172.20.94.6 node6.k8s.novalocal
greeter-79754259-s3bs2 2/2 Running 0 21h 172.20.19.5 jenkins-master.k8s.novalocal
kafka-0 1/1 Running 0 21h 172.20.1.4 node3.k8s.novalocal
mqtt-0 1/1 Running 0 21h 172.20.94.15 node6.k8s.novalocal
mysql-0 2/2 Running 0 21h 172.20.47.7 elk-k8sdata.novalocal
pusher-2834145138-lfs21 2/2 Running 0 21h 172.20.19.6 jenkins-master.k8s.novalocal
recovery-261893050-70s3w 2/2 Running 0 21h 172.20.32.13 node4.k8s.novalocal
redis-0 1/1 Running 0 21h 172.20.4.5 lb1.k8s.novalocal
robot-1929938921-6lz6f 2/2 Running 0 21h 172.20.47.8 elk-k8sdata.novalocal
scheduler-3437011440-rsnj6 2/2 Running 0 21h 172.20.5.10 db.k8s.novalocal
valuation-2088176974-5kwbr 2/2 Running 0 21h 172.20.94.20 node6.k8s.novalocal
zookeeper-0 1/1 Running 0 21h 172.20.4.4 lb1.k8s.novalocal
注意,如何操作用户自己有权限的空间,必需填写default-namespace.conf
注意,当gitlab master分支有合并的时候,目前我们ci自动会构建编译最新的jar版本,推送至nexus仓库,k8s容器里的jar包可以指定更新
k8s init-yml #初始化生成用户自己本人的yml文件
k8s get_all #查看用户自己本人空间下的所有运行的容器
k8s create_all #创建用户自己本人所有服务
k8s delall_app #删除本人空间下所有app服务,除基础服务mysql、 consul、 kafka、 redis、 zookeeper、mqtt 以外的所有服务
k8s apply api #修改了用户自己本人yml配置文件,应用配置生效
k8s create api #用户自己本人空间下创建一个api服务
k8s delete api #用户自己本人空间下删除一个api服务
k8s scale api 2 #用户自己本人空间下把api服务扩容成2个pod
k8s login api #用户本人空间下登录api所在的docker容器
k8s logs api #用户自己本人空间用tail -f 命令的方式查看容器内/home/deploy/api/logs/api.log 的日志
k8s error-logs api #用户自己本人空间用tail -f 命令的方式查看容器内/home/deploy/api/logs/api.error.log 的日志
k8s clean api #如果编译出错,在用户自己本人空间用gradlew clean清理命令的方式清理编译
k8s push_jar #更新本人空间下所有容器的jar包版本,重启所有容器,默认拉取backend / push-envelope -git最终版本,该版本为合并编译成功后的最新版本号
k8s push_jar 20170927-1731 #选择指定的jar版本号20170927-1731 进行更新 ,重启所有容器
k8s reinit-mysql #重新更新所有容器jar版本后api无法启动,清空用户空间下的数据库,重新创建导入数据
批量操作
k8s scale api-geo 2 #在dev用户下把api和geo 扩容
k8s delete api-geo #在dev用户下删除api 和geo服务
k8s create api-geo #在dev用户下创建api和geo服务
所有人员通用命令,要操作某个用户的资源,必需先生成所需要的yml文件
但是必需指定第二个参数名dev test stage等。
k8s stage init-yml #初始化生成stage用户的yml文件 注意要操作stage用户的容器要先成配置文件
k8s test init-yml #初始化生成test空间的yml文件
k8s dev init-yml #初始化生成dev空间的yml文件
k8s dev get_all #查看dev用户空间下的所有运行的容器
k8s dev create_all #创建dev空间下所有服务
k8s dev delall_app #删除dev空间下的app服务,除基础服务mysql、 consul、 kafka、 redis、 zookeeper、mqtt 以外的所有服务
k8s dev apply api #修改了yml配置文件,应用配置生效
k8s dev create api #dev空间下创建一个api服务
k8s dev delete api #dev空间下删除一个api服务
k8s dev scale api 2 #dev空间下把api服务扩容成2个pod
k8s dev login api #dev空间下登录api所在的docker容器
k8s dev logs api #dev空间用tail -f 命令的方式查看容器内/home/deploy/api/logs/api.log 的日志
k8s dev error-logs api #dev空间用tail -f 命令的方式查看容器内/home/deploy/api/logs/api.error.log 的日志
k8s dev push_jar #更新dev空间下所有容器的jar包版本,重启所有容器,默认拉取backend /-git最终版本,该版本为合并编译成功后的最新版本号
k8s dev push_jar 20170927-1731 #选择指定的jar版本号20170927-1731 进行更新 ,重启所有容器
k8s dev clean api #如果编译出错,dev用户空间用gradlew clean清理命令的方式清理编译
k8s dev reinit-mysql #重新更新所有容器jar版本后api无法启动,清空dev空间下的数据库,重新创建导入数据
批量操作
k8s dev scale api-geo 2 #在dev空间把api和geo 扩容
k8s dev delete api-geo #在dev空间删除api 和geo服务
k8s dev create api-geo #在dev空间下创建api和geo服务
管理员专用命令,注意管理员第二个参数一定要填
k8s dev create_rsync #创建dev空间的rsync配置
k8s dev create_passwd #创建dev空间的解压密码下发密钥
k8s dev create rbac #创建dev空间的集群授权认证
k8s dev delete rbac #删除dev空间的集群授权认证
k8s dev delete_all #删除dev空间下所有服务
k8s集群规划和问题总结
- 集群资源规划request +limit+maxpods+eviction参数 需要计算好再配置,配置有问题可能导致资源利用均,一部份利用过高,一部份利用过低。
- k8s node节点一定要留有足够的磁盘空间,跟据pod个数和image大小决定磁盘空间数。
- jdk无法获取正确的cpu数,默认获取的是宿主机cpu,会致创建的线程数过多,系统崩溃, 可以通过:https://github.com/obmarg/libsysconfcpus.git 解决。
if [ "x$CONTAINER_CORE_LIMIT" != "x" ]; then
LIBSYSCONFCPUS="$CONTAINER_CORE_LIMIT"
if [ ${LIBSYSCONFCPUS} -lt 2 ]; then
LIBSYSCONFCPUS=2
fi
export LIBSYSCONFCPUS
fi
export LD_PRELOAD="/usr/local/lib/libsysconfcpus.so:$LD_PRELOAD"
- nfs-server一定要用async 充份利用缓存加快写入速度,注意内核 版本bug。
- 应用产生的日志必需要设置轮转数和大小,防止过大日志撑暴宿主机磁盘。
- 发布版本越多,随着下载镜像版本越来越多,磁盘会撑爆,合理配置kubelet image gc参数,配置gc回收优化磁盘空间。
- docker-ce以前的版本经常会出现docker 失控,使用过程中整个节点容器无法删除,无法创建,只能重启,对业务影响很大,建议全部更新到18-ce版本,和k8s容性更好。
- 节点的亲和性和反亲和Affinity一定要提前规划好,为了达到高可用目的,多副本必需配置。
- 应用异常检测,跟据实际情况配置探针readinessProbe,livenessProbe 防止应用假死,k8s提前剔除有问题的pod容器。
更多推荐
所有评论(0)