姑且称之为“解决方案”吧(自己觉得就是部分devops工具的基本使用)
指路牌
适合小规模团队的dev/ops“方案”
解决项目环境或版本问题
Nginx在同一端口部署多个服务
免费通配https证书
私有docker仓库搭建
背景
在介绍方案前需要先简单描述一下我所在团队的工作特点,我所处团队主要从事创新案例相关开发,每年会不止一个项目开发完成,团队内开发人员皆为软件工程师,每人专注具有各自的特长领域,无专门运维人员。由于开发中的交互主要通过http协议完成,每套代码的只有自己负责,开发完没有代码质量的审核等流程,因此代码存在不规范等问题。
让我设计这个方案的最直接原因有两个:
- 接手他人代码,再部署各种踩坑
- 多人同时使用服务器导致服务器管理混乱
对于第一条,我曾碰到为了部署一套过往的项目,需要花费近1个月去阅读和修改代码,这其中无外乎几个原因:环境版本问题,代码散乱的混入与其他代码或服务器相关的硬编码(如url),代码文档不健全,框架不熟悉。对于第二条,主要原因是开发人员对服务器的使用熟练度与规范水平不一,常会发生如端口从3000~10000离散的乱开,文件权限随意设置777,项目部署路径不统一,配置文件编写格式不统一等一系列问题。
虽然我经常会提到代码不规范的问题,实际上我知道要求所有代码都规范是不现实的,因为自觉得的自己的代码都很“稀烂”,就像”完美的道德“,提倡,但永远达不到(起码不是所有人),所以提出这个方案就是想将上面提到的问题通过docker都封闭在容器内,通过脚本用自动化来取代人为配置,从而做到服务器的管理统一,项目再部署的顺滑。
“方案”简介
先来看一下整体结构图,项目一共使用了以下工具:
- OpenResty(Nginx) :不严谨等价Nginx,统一对外,负责支持https
- Harbor : 基于docker registry的docker私有仓库管理系统
- Jenkins : 应该都知道不多介绍
- Docker : 应该都知道不多介绍
- Let's Encryp : 支持免费的https通配证书
- 自己开发的脚本 : 实现拉取docker镜像与添加和reload Nginx配置
我们通过一个项目的开发流程来区分一下这个方案给开发带来的差别。
过往项目开发流程概括如下:
开发->测试->代码上传github->登陆服务器->github拉取代码->(安装依赖模块)->Nginx添加配置->Nginx配置测试reload->部署完成
使用方案之后变为:
开发->测试->代码上传github->本地打包docker->镜像上传Harbor->Jenkins执行脚本->部署完成
从流程可以发现在开发阶段增加了打包docker镜像的工作,而部署阶段可以完全不登陆服务器,且不在需要关心Nginx配置等问题。或者更直观一点,在Jenkis的脚本中传入3个参数后,点击build之后通过url就可以直接访问到服务了。
3个参数分别为:二级域名,docker镜像的url,docker内服务所起端口,脚本将根据以上三个参数来自动拉取docker镜像,运行,并在Nginx内添加配置(包含http证书),需要指出两点:
- 所有的服务都是使用443端口,所有url不需要带端口号,如此便可以支持类似微信公众号,微信小程序等ecosystem要求的要求https且不允许带端口的要求
- 在已有服务器与域名的情况下,这个方案是没有任何额外开支的,包括https通配证书
下面来逐个详细每个模块的选取考量
OpenResty(Nginx)
本质上就是Nginx,OpenResty在Nginx的基础上默认带有更多功能模块,如lua,能极大的强化的Nginx的功能,但实际上该方案中都没有使用到,而之所以最乎会探索到OpenResty,很大一部分原因是因为解决“如何在一个端口起多个服务”这个后来卡了我近3周的问题,而OpenResty指出的lua,是当时探索失败,或者说没有探索完的途径之一。虽然在本例中完全可以使用Nginx代替,但是我依旧推荐安装OpenResty,因为Ubuntu apt安装的Nginx版本比较旧。
Harbor
类似Github的私有仓库,Docker本身也提供有私有镜像仓库,为什么没有使用官方的呢?原因有两个,一是贵,二是公司项目不适合放在公网仓库。而公司中间也了解过公司内部也有私有仓库服务(JFrog),但是简单调研后发现外网无法访问(如果有人知道可以外网访问的途径请告诉我),下一步就调研起了docker registry,docker官方提供的创建私有仓库的镜像,成功了,但是没有账号管理功能,他人依旧可以根据url拉取到镜像,最后找到了Harbor,具有GUI与账号管理功能的私有仓库,功能类似Docker Hub,而实际上Harbor是基于docker registry,安装完Harbor后使用docker container ls
命令会发现其会自动起一个docker registry
docker registry 是支持将镜像存储路径映射到ECS的,如此备份与迁移将更容易.
Jenkins
Jenkins开发人员应该都熟悉,不多做介绍,我第一次接触Jenkins是为了使用github提供的webhook功能,当时就有一种‘杀鸡用牛刀’的感觉,而这次同样有这种感觉,因为Jenkins在方案中只是起到了执行脚本,查看控制台输出的功能,而这些功能实际上自己简单写几行代码也能实现。(当初调研webhhok使用express 20行代码实现了相同的功能),二者这把‘牛刀’需要吃掉将近1~2G的内存。只能说这个方案完全没有发挥出Jenkins的潜能,选择它的原因是因为其的普遍性。
Docker
个人觉得是现在程序员的基本功,除了docker打包以外,方案中会较多用到docker login
,docker push
, docker pull
三个命令
Let's Encryp
Let's Encryp救我于水火,尝试了各种方案用于解决“如何实现在一个端口上起多个服务”失败准备放弃的时候,发现Let's Encryp是一个开源的CA机构,起提供的免费https通配证书,于是顺利帮我解决最后一个难题没让整个方案成为可能,Let's Encryp不是一个工具,而是一个机构,起提供证书服务,根据起客服端可以在命令行就完整https证书的申请与验证,貌似还提供自动续签功能?(没验证)
自己开发的脚本
Github Url
如果以上部分都是安装与配置的话,本模块就需要自己写代码开发了,脚本实现了docker部署与Nginx配置的功能,起本身也不复杂,读取三个参数:二级域名,docker镜像的url,docker内服务所起端口。脚本本身会维护一个端口映射表,调用后将根据docker镜像的url拉取镜像,并根据docker内服务所起端口启动docker镜像,映射到ECS的端口有ECS由脚本记录,最后根据模版向Nginx添加配置,配置在443监听二级域名,proxy_pass到指定的docker在ECS的映射端口。
ps:我贴出的的自己的代码只是最雏形的版本,能够跑通功能,但还没添加参数验证,镜像验证,失败处理等代码。
优缺点分析
方案的分析我觉得需要份服务器与开发人员两个维度分析
首先对于开发者,最直观的一点就是将部署工作简化,而在开发阶段增加的容器化的步骤,本质上讲,开发者并没有逃过“部署”的操作,只是从原本的部署到服务器,转嫁到通过Dockerfile部署到容器内,确实减少工作量是https的配置,且不需要再登陆服务器,所以我个人认为,由于docker学习带来的学习成本,开发人员的工作量总体变化不大。(但是作为趋势我认为docker的学习成本是不应该算作方案的缺点的,因为docker应该是基本功,在默认会使用docker的前提下,我认为工作量是减少了),但是当出现项目交接与服务器需要再部署的时候,docker的强大就体现出来了,也即是所谓的‘一秒部署’,类似我之前的需要花费1个月解决硬编码与环境问题的情况讲不复存在,而因此剩下的人日是巨大的.(二次开发不包含在内,任何二次开发接受人都需要读改代码)
对与服务器,好处是服务器的管理会更统一,不会出现权限与配置混乱的情况,但!服务器的符合与要求会提高,由于使用封装更高的docker‘代替’git作为最小管理单位,docker的体积是百兆位单位的,二git拉取的代码则笑得多,所以服务器的存储空间,与网络带宽都需要相应的提高,否则会出现镜像多了后存储空间不够,以及上传拉取速度过慢的情况。
如何实现在一个端口上起多个服务
作为困扰我最久的问题,单独列出来探索经过与最后的成功的方式
这个需求的起因是有一次展会同时有两个与微信相关的项目,二微信的部分功能要求必须使用https协议,且不允许使用端口,这就导致443成为了挣多点,如果在443端口起多个服务?
首先记住一个URL格式:https://domain.com/path
最早的思路是通过在Nginx的一个server中配置多个location,想通过location的路径分别proxy_pass到不同的系统,也就是根据/path来转发到不同的系统。这个思路是失败的最彻底的,一开始我认为是我的没有理解透测location的配置规则,因此排列组合了近50种测试用例,每次修改配置reload,最终都以失败告终,之所以在这条路卡如此之久,是因为在网上不同的论坛搜到了大量的blog,通过这个思路的写法,“声称”他们成功了,在卡了很久之后我是很愤怒的,因为我想知道他们到底是怎么成功了,又或者他们只是纯靠想象写出来的?无论是出于获取点击量,或者只是自己没有学明白而发blog,我觉得都是不知识分享类blog该做的。而前者是可耻的。
第二条探索路线有我与另一位同事并行进行,我的途径是使用Nginx的支持的lua模块,想要通过lua编写分支语句,在一个loaction下,根据/path转发到不同的系统,当然也是失败的。而我的同事使用的是Nginx的负载均衡模块用到的upsteam模块,结果也失败了。
第一条曙光是rewrite带来的,在探索lua的期间发现Nginx本身也是支持if模块的,只是这个功能看起来让人浮想联翩,但实际使用却有很多闲置,一个专门次形容Nginx的if模块“ifisevil : If Is Evil”,但是由于rewrite模块是少数可以安全是使用在if模块的,因此就开始了探索,最后的写法结构就是在1个server中有1个location,location内有多个if语句,格式如下:
if ($uri ~* /path){
rewrite "^/path(.*)$" http://sapdis.cn:PORT$1 break;
}
也就是在接到请求后,将/path
后的部分增则匹配出来后添加到目标url的后面,结果上来说对于一般系统成功了,但是倒在了微信相关应用的测试上,因为rewrite本质上是实现了一个301或302的跳转,最后的请求实际上依旧是带有端口号的,而微信是不允许重定向以及端口的。
到此我原本是准备放弃的,因为我想到的最后方案是自己写一套代码,在代码里实现请求的转发,也就是写一个proxy,但是卡了近3周实在没有力气写了
最后一条路,也是成功的,就是通配证书,只是上这个是最早思路之一,之所以排除是因为通配证书需要付费,阿里云提供的最便宜的通配证书买年1250RMB,因此虽然知道可行性是最高的,但是直接pass了。而而然搜索之后发现Let's Encryp在2018年协议升级后开始支持免费通配证书了,知道这个消息后,困扰了很久的问题半天时间就解决了,最后的思路是通过编写多个sever,同时监听443,通过server_name的二级域名来区分请求的系统,从而实现对不同服务器的转发,也就是最后的url格式如下:
https://subdomain.domain.com
server {
listen 443 ssl;
server_name SUBDOMAIN.sapdis.cn;
ssl_certificate cert/sapdis.cer;
ssl_certificate_key cert/sapdis.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
client_max_body_size 0;
location / {
proxy_pass TARGETURL;
}
}
后来仔细回想了整个问题结果的过程,也许一开始路子就走歪了,因为url的路径部分应该只指向服务后,二路径部分应该是对应服务内的不同api,而试图通过路径的组成来区分系统也许本身就是“歪门邪道”。不过通过这个过程,对Nginx,http的进一步加深了,也算收获颇丰。