2019独角兽企业重金招聘Python工程师标准>>>
一、前言
说实话,Rancher的官方文档真的很全,围绕着UI的方方面面都面面俱到。但看多了却发现,很多东西都浅尝辄止,尤其某个东西实在不懂的时候,看文档只能让自己更没头绪。所以此文的目的是方便自己能够在需要的时候回忆出点点滴滴。
Rancher1.6的傻瓜式操作给了我很大的帮助,也帮我赢得了大部分运维的支持,最终得以施展。但Rancher2.0无论是开发版还是beta版,都并不是那么友好,总是莫名其妙的出现各种各样的问题。后来因为工作的调动,没有继续研究新版。不过现在终于有时间了,当前最新版本2.1.4,所以此文的基础也就是2.1.4。
另外,本文不会花篇幅去介绍如何安装或者解决安装中的问题,但涉及到的,我都会附上链接。本文主要的作用一个是备忘,还一个是手册。如果以后回头看这篇文章,发现忘了的看一遍就回忆起来了,照着做一遍的环境就搭好了那就完美了。
二、先决
有必要在这里提前声明一下先决条件(并不是说实际上就是这个数据,而是我的环境是这个数据)。从Rancher1.6开始到现在的2.1.4,每一个版本都用过。不同的版本对先决条件敏感,如1.6是敏感度最迟钝的,只要保证OS内核3.1.10以上(含)即可。至于Docker版本,在查阅Rancher文档的时候,很多地方明确指出最高支持版本17.02,但实际使用过程中,只要是最新版本的都可以。以下是具体的环境说明:
物理机OS:Ubuntu 18.04
Docker:18.09
harbor:1.7.1
Gitlab:10.4.4
三、准备
从零开始,现有三台物理机,两台服务器,一台笔记本,服务器上只跑一个虚拟机实例,笔记本跑两个虚拟机实例。但需要注意的是:如果需要拷贝虚拟机,必须是同物理机上的拷贝,而不能夸物理机拷贝,哪怕用了什么ovf虚拟机格式,也都不行,因为在实际使用的时候会出现一些莫名的错误,也可能不出,全靠运气。以下是主要步骤(可直接点击穿越到教程,教程来源于网络):
1、更新源(apt-get update && apt-get upgrade)
2、修改vi命令(默认vi命令不友好)
3、设置root用户以及启用root远程登录
4、设置limit
5、安装docker
6、配置加速器(加速器可用daoclould也可用阿里的,这里贴出的是daoclould的)
7、设置机器域名host指向(如果是直接使用IP访问的话,跳过这个)
8.1、安装rancher
sudo docker run -d --restart=unless-stopped -p 80:80 -p 443:443 rancher/rancher
8.2、安装harbor
9、登录Rancher创建集群导入机器
10、所有机器harbor登录授权(如果不需要自己手动pull私服上的镜像,此步跳过)
额外需要注意的是:
1、最新版本的docker默认采用https方式拉取镜像(确切的说是自1.3.2版本开始),如果使用http,那就需要在机器上添加过滤,详情点击。
2、自授权的https证书,需要在机器上信任,信任方式点此。
3、Rancher内部是k8s,k8s集群中,不会读取宿主机的证书。故在宿主机配置了证书信任,不会渗透到k8s集群。也就是说在宿主机信任证书,对于k8s来说,没有任何影响。这个产生一个很大问题,就是在Rancher里配置了镜像库后,Rancher无法登陆到镜像库,原因就是CA证书不被信任。这直接导致私有镜像私服被废了,除非公司花钱买证书,或者使用如阿里云镜像私服这样第三方服务。阿里云地址点此。
四、概念说明
这一部分是说明1.6和2.0两部分,Rancher的概念,目的是着重理解Rancher。
前段时间看了本书叫《码农翻身:用故事给技术加点料》,这个思路很清奇,久久不能忘。遂本章也用说故事的方式来阐明概念。
4.1白手起家的农场主(1.x)
小明的志向是当一个农场主。虽然他从来没干过,但是他现在准备干了。
于是他买了一块地,首先他用栅栏围了起来,防止牛羊越狱,同时也防止可恶的豺狼豹子偷猎牛羊。然后小明对圈内的地进行的划分,A区域B区域……,这样可以方便管理。每个区域他又建了很多棚舍,不过棚舍之间是互通的,这样牛羊就不会寂寞,可以串串门。
万事俱备只差牛羊,小明考虑直接去牛羊中心,路途遥远,倒不如去最近的代理商,代理商就像超市,把牛羊从厂家运送过来,这样省时又省力。为了保险起见,小明只买了几百只牛羊,然后赶到自己规划好的农场。
其实小明的工作很简单,定时检查一下牛羊的状态,是不是饿了渴了或者生病了又或者死亡了,不过如果牛羊死亡了,小明就必须再买一头回来。有时候小明还会给牛羊做个造型美化一下,小明说这叫升级,当然如果美化失败了,小明还会恢复原样,小明又叫这个回滚。
小明很满意自己的农场,但觉得一个人有点无聊,于是他又建立了一个站台,站在站台上可以看到某个区域的每个棚舍,就像动物园一样。然后开放给大家,不仅能赚点外快,还不会觉得无聊。
从此,小明过上了幸福的农场主生活,实现了人生理想。
故事到此结束,现在解释一下文中的概念:
1、小明:rancher的master节点
2、栅栏:rancher集群
3、区域:集群
4、棚舍:项目
5、牛羊:容器
6、牛羊中心:docker hub镜像中心
7、代理商:国内镜像如阿里云
8、升级:镜像升级
9、回滚:镜像回滚
10、站台:负载均衡(HA)
11、小明的工作:cattle编排引擎
4.2高速发展的农场(2.x)
小明的幸福没过多久,就出现问题了。第一,小明每天不仅要进行繁琐的工作,还要接待到访的游客,忙得团团转。第二、牧场太小了,访客太多了,每次都有人抱怨等待的时间太久了。小明痛定思痛决定要改善这两个问题,好在经营了一段时间,手里攒了不少积蓄。
首先,他扩大了牧场,区域划分保持不变,以前一个大棚舍,现在建好几个大棚舍,但棚舍里做好规划,不同棚舍养不同的牛羊,同时棚舍之间是独立的,防止牛羊乱窜,但一样的同一个棚舍还是不禁止的。另外,大棚舍里的小棚舍,也得改造,每次游客来都反应脏乱差。所以,给每一头牛羊建立单独的房间,再装饰的非常精美,除此之外,每个房间还会配备专人管理,最后起个洋气的名字,叫pod。
然后,雇佣一大批的人,把小明以前的工作都细化分工下去,比如待在pod里,负责牛羊的管理,比如升级和回滚。然后每个大棚舍自治,牛羊的采购、参观等等独立化,这样管理起来更方便,同时如果一个大棚舍出现问题,也不会影响到别的大棚舍。
接着站台也要优化一下,以前是固定的几个站台,大家都按序排队。现在不用那么麻烦了,因为大棚舍自治了,每个大棚舍单独售票,所以只需要安排几个迎宾,游客来了,问一下去哪个大棚舍,然后直接带过去就行了,大大减少了访客的等待时间。
最后,由于规模相比之前不可同日而语,各个流程小明无法手动管理,于是小明又规划了一个叫流程的东西,想要干什么东西话,只需要按照流程走就对了。
至此,所有问题都解决了,并且小明也解放了,每天只需要巡视工作就成了,小日子过得不要不要得。
故事到此结束,现在解释一下文中的概念:
1、小明:rancher的master节点
2、栅栏:rancher集群
3、区域:集群
4、大棚舍:命名空间
5、小棚舍:项目
6、pod:pod,k8s对容器的包装,负责容器的生命周期
7、牛羊中心:docker hub镜像中心
8、代理商:国内镜像如阿里云
9、升级:镜像升级
10、回滚:镜像回滚
11、站台:负载均衡(nginx),以前会在固定的IP上,现在只要在集群的任何一台机器上即可,因为会有专门的“迎宾”处理
12、为小明工作的人:k8s编排引擎
13、流程:流水线(CI/CD)
五、场景实例
有一个以JAVA编写的微服务,现在要部署上线。
5.1规划
首先,环境分为本地环境(dev)、测试环境(test)、预发布环境(uat)、线上环境(live)。
然后,根据业务划分命名空间,比如后台统一为service,手机客户端统一为app
然后,根据业务划分项目,如用户管理的后台服务项目,则命名为user-manager
然后,根据代码项目划分服务,比如认证服务是user-auth,登录是user-login
然后,根据实际代码,配置负责均衡暴露服务,为外界提供接口
同时,配备日志采集,可以统一查看各个项目的日志
5.2流水线(CI/CD)
简单说下原理,Rancher按以下步骤运行:
1、Rancher启动一个jenkins镜像,此镜像可以通过配置一个负载均衡来暴露出去访问后台界面,用户名是admin,密码是容器里的环境变量ADMIN_PASSWORD,默认是cgfh9ljx7dtx7s2vmqf7r9xcf6zj45s5lxcwg95zmtqz9n58mg84pc,如果启动流水线报错,登录jenkins,进去系统设置->全局安全设置,找到“防止跨站点请求伪造”,确定勾选,然后点击保存(如果已勾选,那么勾选掉,保存,再编辑,勾选,保存),再重新启动流水线即可。
2、将流水线任务生成groovy脚本,然后在jenkins上创建任务并执行。
3、流水线的每一个阶段对应一个镜像,Rancher会启动对应的容器,执行流水线配置的流程。
一个典型的jenkins流水线脚本,类似如下:
import org.jenkinsci.plugins.pipeline.modeldefinition.Utils
def label = "buildpod.${env.JOB_NAME}.${env.BUILD_NUMBER}".replace('-', '_').replace('/', '_')
podTemplate(label: label, namespace: 'p-8j2pf-pipeline', instanceCap: 1, serviceAccount: 'jenkins',volumes: [emptyDirVolume(mountPath: '/var/lib/docker', memory: false), secretVolume(mountPath: '/etc/docker/certs.d/docker-registry.p-8j2pf-pipeline', secretName: 'registry-crt')], containers: [
containerTemplate(name: 'step-0-0', image: 'rancher/pipeline-tools:v0.1.0', ttyEnabled: true, command: 'cat' , envVars: [
envVar(key: 'CICD_GIT_REF', value: 'refs/heads/master'),envVar(key: 'CICD_GIT_BRANCH', value: 'master'),envVar(key: 'CICD_PIPELINE_ID', value: 'p-dd58c'),envVar(key: 'CICD_CLUSTER_ID', value: 'c-gb2hf'),envVar(key: 'CICD_LOCAL_REGISTRY', value: '127.0.0.1:34350'),envVar(key: 'CICD_GIT_COMMIT', value: '6795394'),envVar(key: 'CICD_GIT_URL', value: 'http://git.lumiai.top/york/hyperloop-baseapi.git'),envVar(key: 'CICD_TRIGGER_TYPE', value: 'user'),envVar(key: 'CICD_EVENT', value: ''),envVar(key: 'CICD_EXECUTION_ID', value: 'p-dd58c-27'),envVar(key: 'CICD_EXECUTION_SEQUENCE', value: '27'),envVar(key: 'CICD_PROJECT_ID', value: 'p-8j2pf'),envVar(key: 'CICD_GIT_REPO_NAME', value: 'hyperloop-baseapi'),
]),containerTemplate(name: 'step-1-0', image: 'maven:3.6.0-jdk-8-alpine', ttyEnabled: true, command: 'cat' , envVars: [
envVar(key: 'CICD_GIT_COMMIT', value: '6795394'),envVar(key: 'CICD_GIT_REPO_NAME', value: 'hyperloop-baseapi'),envVar(key: 'CICD_GIT_REF', value: 'refs/heads/master'),envVar(key: 'CICD_GIT_URL', value: 'http://git.lumiai.top/york/hyperloop-baseapi.git'),envVar(key: 'CICD_PIPELINE_ID', value: 'p-dd58c'),envVar(key: 'CICD_EVENT', value: ''),envVar(key: 'CICD_CLUSTER_ID', value: 'c-gb2hf'),envVar(key: 'CICD_GIT_BRANCH', value: 'master'),envVar(key: 'CICD_TRIGGER_TYPE', value: 'user'),envVar(key: 'CICD_EXECUTION_ID', value: 'p-dd58c-27'),envVar(key: 'CICD_EXECUTION_SEQUENCE', value: '27'),envVar(key: 'CICD_PROJECT_ID', value: 'p-8j2pf'),envVar(key: 'CICD_LOCAL_REGISTRY', value: '127.0.0.1:34350'),
]),containerTemplate(name: 'step-2-0', image: 'rancher/jenkins-plugins-docker:17.12', ttyEnabled: true, command: 'cat' , privileged: true, envVars: [
envVar(key: 'CICD_GIT_REF', value: 'refs/heads/master'),envVar(key: 'CICD_PIPELINE_ID', value: 'p-dd58c'),envVar(key: 'CICD_EXECUTION_ID', value: 'p-dd58c-27'),envVar(key: 'CICD_EXECUTION_SEQUENCE', value: '27'),envVar(key: 'CICD_PROJECT_ID', value: 'p-8j2pf'),envVar(key: 'CICD_CLUSTER_ID', value: 'c-gb2hf'),envVar(key: 'CICD_LOCAL_REGISTRY', value: '127.0.0.1:34350'),envVar(key: 'CICD_GIT_COMMIT', value: '6795394'),envVar(key: 'CICD_GIT_REPO_NAME', value: 'hyperloop-baseapi'),envVar(key: 'CICD_GIT_BRANCH', value: 'master'),envVar(key: 'CICD_GIT_URL', value: 'http://git.lumiai.top/york/hyperloop-baseapi.git'),envVar(key: 'CICD_TRIGGER_TYPE', value: 'user'),envVar(key: 'CICD_EVENT', value: ''),envVar(key: 'PLUGIN_DOCKERFILE', value: './Dockerfile'),envVar(key: 'PLUGIN_CONTEXT', value: '.'),envVar(key: 'DOCKER_REGISTRY', value: '127.0.0.1:34350'),envVar(key: 'PLUGIN_REPO', value: '127.0.0.1:34350/scc/hyperloop/baseapi'),envVar(key: 'PLUGIN_TAG', value: 'dev'),secretEnvVar(key: 'DOCKER_USERNAME', secretName: 'p-8j2pf-12700134350', secretKey: 'username'),secretEnvVar(key: 'DOCKER_PASSWORD', secretName: 'p-8j2pf-12700134350', secretKey: 'password'),
]),
containerTemplate(name: 'jnlp', image: 'rancher/jenkins-jnlp-slave:3.10-1-alpine', envVars: [
envVar(key: 'JENKINS_URL', value: 'http://jenkins:8080')], args: '${computer.jnlpmac} ${computer.name}', ttyEnabled: false)], yaml: """
apiVersion: v1
kind: Pod
metadata:
labels:
app: jenkins
execution: p-dd58c-27
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- jenkins
topologyKey: kubernetes.io/hostname
"""
) {
node(label) {
timestamps {
timeout(60) {
stage('Clone'){
parallel 'step-0-0': {
stage('step-0-0'){
container(name: 'step-0-0') {
checkout([$class: 'GitSCM', branches: [[name: 'local/temp']], userRemoteConfigs: [[url: 'http://git.lumiai.top/york/hyperloop-baseapi.git', refspec: '+refs/heads/master:refs/remotes/local/temp', credentialsId: 'p-dd58c-27']]])
}
}
}
}
stage('打包'){
parallel 'step-1-0': {
stage('step-1-0'){
container(name: 'step-1-0') {
sh '''
echo "haha">baseapi.jar '''
}
}
}
}
stage('发布'){
parallel 'step-2-0': {
stage('step-2-0'){
container(name: 'step-2-0') {
sh '''/usr/local/bin/dockerd-entrypoint.sh /bin/drone-docker'''
}
}
}
}
}
}
}
}
好了,说完原理,下面实操。
进入到Rancher管理平台,依次进入dev(集群)->service(命名空间),然后点击流水线。
5.2.1源码管理
源码管理使用本地Gitlab,代码语言是Java,使用maven构建,提供REST风格的API接口。
目前Rancher只支持接入单个代码库,不支持多代码库同时接入。打开Gitlab,点击个人设置->应用,然后配置应用信息,拿到应用ID和应用密钥,最后回到Rancher输入gitlab地址、应用ID和应用密钥即可接入。
5.2.3源码打包
启用任意代码项目后,流水线首页就会出现对应的数据,点击编辑。
编辑页面,可操作的东西不多,但很简明易懂。
第一步克隆代码,这一步是默认的也是必须和无法编辑的。
点击添加阶段,输入阶段名,如“打包”,点击完成
点击添加步骤,步骤类型分三类,一类是运行脚本,一类是构建并发布镜像,最后一类是部署YAML,每一个阶段可以添加很多步骤。这里点击运行脚本,然后在下面镜像那里输入maven:3.6.0-jdk-8-alpine,输入完成后会出现提示框maven:3.6.0-jdk-8-alpine (Custom),鼠标点击即可,最后在脚本那里输入mvn clean package。
5.2.4镜像发布
打包完成后,再创建一个阶段,叫“镜像发布”,再点击添加步骤。
步骤类型是“构建并发布镜像”,下面Dockerfile路径指的是代码中基于项目根路径的Dockerfile路径,镜像名称注意不用写镜像私服地址,但要带有仓库中对应项目名,比如harbor中我有一个项目叫shy,那么这里应该输入shy/xxx:[xxx],再勾选下面的“推送镜像到远端镜像仓库”,选择对应的镜像库即可。
特别需要注意的是,这一步会启动一个叫rancher/jenkins-plugins-docker:17.12的镜像,这个镜像会使用默认的https推送镜像,如果使用了Harbor,但是自签证书,那么这一步永远过不去,必须是合法的CA证书。或者使用阿里云的镜像私服,那个CA证书肯定得合法的。
5.2.5上线部署
镜像发布后,在创建一个阶段,叫“上线部署”,再点击添加步骤。
步骤类型是“部署YAML”,下面YAML路径同样指的是代码中基于项目根路径的YAML路径。然后点击添加, 在流水线配置的右下角有个“显示高级选项”,打开他,配置触发规则。也可不配置,每次手动执行。
最后点击完成,即创建完一个完整的流水线。
5.3日志采集
一个服务不可能只有1个实例,多实例就导致日志的排查比较困难,因为不知道访问落到了哪个实例上,所以一个集中式的日志中心是很有必要的。rancher针对每一个命名空间都提供了一个专门的日志采集接口,只需要配置日志输出即可。
打开所有的应用商店,搜索efk,然后点击安装。
接着,命名空间视图下,点击资源->日志,选择Elasticsearch,输入刚刚创建的es的地址,确定索引前缀,保存即可。指的注意的是,这里的日志粒度还是有点粗的,只到项目这一层级,实际使用的时候,可以多建项目,每个项目一类服务的方式,规避粒度粗的问题。
5.4负载均衡
1.x的Rancher,事先是不知道实际的IP地址的,每次都是先配置负载均衡,拿到IP再配置DNS解析。
2.x的Rancher,不用事先知道具体的IP了,访问集群内的任何一台机器即可,并且再也不用担心80端口不够用的情况。但由于这个版本放弃了HA,采用的nginx,并且不支持自定义配置,导致目前只支持L7层的负载均衡。
5.5机器扩容
Rancher的扩容非常容易,按照第三章的前7条执行,然后将此时的镜像打包成系统镜像,以后直接拿这个镜像安装系统。安装完成后,直接在Rancher界面上添加主机,即可实现扩容,另外2.x的版本新主机的管理不需要手动进行,k8s会主动进行管理。
六、最佳实践
这里说的我们线上集群的情况,并进行了一定得推测。另外,docker最佳搭档就是所有无状态的服务,Rancher同理也最适合无状态的服务,对于有状态的,如mysql,最好不要丢到rancher或者说docker上运行,不稳定是其次,数据丢失可怕了。
6.1机器配置
物理机很重要,他是载体,决定了容器的情况。
CPU:最低单U8核,越多越好,看具体情况。
内存:32G是最低了,再少跑不了多少容器,我们是64G。
硬盘:机械盘RAID5跑不了(这玩意读取性能不高为啥用他?因为生产中硬盘坏了很常见啊,呵呵),当然最好的还是固态,但那玩意成本太高。
网卡:千兆的应该是标配了吧
6.2节点数量
1.x受限于cattle的编排能力,节点数最好保持在20台左右,容器总量500以下,多了cattle就处理不过来了。
2.x对节点数量没限制,但是节点的类型需要注意下:
worker:是工作节点,不限数量,我们是除rancher本身外,其它都是worker
control:集群控制节点,负责每个pod的控制,我们是设置了总机器数量的数量
etcd:k-v存储,数量必须是奇数,我们是设置了总机器数量的1/3
也就是说每3台机器,有一台的类型是worker和control以及etcd,其它两台是worker和control。
6.3HA
Rancher:主节点必须是高可用的,我们是3个主节点,使用mysql存储数据,中间用mycat代理。
Mysql:跑在物理机上,1主2从。
6.4安全
虽然Rancher是一个密闭的集群,但宿主机被黑,跑在这上面的容器也会遭殃。但如果物理机如果开启了防火墙,却又会导致一些莫名的错误,这时该怎么办呢?
如果有自己的机房和机器,解决方案是:
1、关闭所有主机的防火墙,同时断开一切与外网的连接,相当于一个密闭的局域网
2、将F5架设在局域网的前端,外网可以访问F5,内网也可以访问F5,但外网访问不了内网。然后再将F5打造成一个堡垒机,最后所有请求通过F5转发的对应的服务上。
如果是云服务器,购买机器,不买外网IP,通过云提供商的负载均衡访问所购买的机器,这种和上面是一模一样的。
最后说下登录认证,企业一般使用ldap,或者直接数据库密码存储。但推荐ldap,企业里是非常方便的。