初创公司利用Docker持续集成部署

jopen 9年前

来自:http://dockone.io/article/671

Docker技术炙手可热,但是对于初创企业如何利用Docker这门新技术来解决内网开发或者测试环境的快速部署,解放运维并提高工作效率,尤其在创业 之初人力资源不足的情况下,更为重要。为此我和公司一位开发同学一起研究的关于Docker快速部署应用的解决方案架构,下面是整个方案部署的步骤,由于 资源有限过程中有问题还望大家指正交流,谢谢!

架构图

 初创公司利用Docker持续集成部署

部署说明:

 初创公司利用Docker持续集成部署


注意:
1. 平台部署以虚拟机为基础,除DNS服务器系统为Centos6.5之外,所有虚拟机系统为Centos7.0。
2. VM系统需要设置selinux为允许或者关闭,以及关闭默认系统防火墙。

安装过程:(dns-registry-zk-mesos-marathon-consul-nginx-consul-template)

1)安装docker软件,部署说明中需要docker环境的都需要先安装软件,命令如下:
systemctl stop firewalld;systemctl disable firewalld  setenforce 0  sed -i s/^SELINUX=enforcing/SELINUX=permissive/ /etc/selinux/config  #设置Selinux为允许或者直接关闭。  yum install docker -y  vi /etc/sysconfig/docker  OPTIONS='--selinux-enabled --insecure-registry docker.smart.com'  #添加docker仓库信任  systemctl start docker  #启动docker服务 

2)配置DNS解析smart.com区域文件

DNS服务器配置过程略。
192.168.10.200 docker.smart.com  #解析仓库域名服务器  echo nameserver 192.168.10.86 >>/etc/resolv.conf  #修改所有服务器域名服务器地址

3)配置Docker仓库
/data/docker/registry  #创建docker运行映射目录  docker pull index.alauda.cn/juqkai/registry  #灵雀云拉一个仓库镜像  docker tag index.alauda.cn/juqkai/registry docker.smart.com/base/registry  #修改标签  docker run -d --restart=always -p 80:5000 -v /data/docker/registry:/tmp/registry-dev docker.smart.com/juqkai/registry  #运行registry服务  docker push docker.smart.com/base/registry  #将本地镜像推送到仓库  curl http://docker.smart.com/v1/search  #查看仓库中的相关镜像(可以使用python json模块转换| python -m json.tool)  (可以按照此方法将Jenkins、nginx、zookeeper等镜像拉下来并推送到docker.smart.com仓库)

4)配置zookeeper
mkdir /data/docker/zookeeper/conf -p  #创建配置文件映射目录(统一将docker运行的软件配置文件放到这个目录)


  • cat /data/docker/zookeeper/conf/zoo.cfg
    tickTime=2000    initLimit=10    syncLimit=5    dataDir=/tmp/zookeeper    clientPort=2181    server.1=0.0.0.0:2888:3888    server.2=zk2.paas.smart.com:2888:3888    server.3=zk3.paas.smart.com:2888:3888    
  • /data/docker/zookeeper/data/myid
    echo "1" > /data/docker/zookeeper/data/myid    
  • 如果myid与zoo.cfg配置文件中的server.x相同则后边只能写0.0.0.0,2888为zk通讯端口,3888为选举端口,这里3个zk组成一个集群。
5)配置mesos-master和mesos-slave
rpm -i http://repos.mesosphere.io/el/7/noarch/RPMS/mesosphere-el-repo-7-1.noarch.rpm  yum -y install mesos   mesos-master --ip=192.168.10.203 --hostname=master1.paas.smart.com --work_dir=/var/lib/mesos --quorum=1 --log_dir=/var/log/mesos --port=5050 --zk_session_timeout=120secs --zk=zk://zk1.paas.smart.com:2181,zk2.paas.smart.com:2181,zk3.paas.smart.com:2181/mesos  #启动master


  • 这里--ip为本机 --hostname也为本机主机名 --quorum做为有效果结点数,值为(N+1) / 2,N是master节点数,配置log_dir主要是为了查看mesos日志。
mesos-slave --ip=192.168.10.136 --hostname=192.168.10.136 --log_dir=/var/log/mesos --containerizers=docker,mesos --master=zk://zk1.paas.smart.com:2181,zk2.paas.smart.com:2181,zk3.paas.smart.com:2181/mesos --executor_registration_timeout=5mins  #启动slave 首次启动均在前台,没有问题可以使用nohup放到后台。

  • ip每个主机取值不相同 hostname默认会是主机名,但很多情况不能访问,所以使用主机IP
    如果要在WEB UI上看到日志,需要配置log_dir属性
    executor_registration_timeout容器执行的超时时间,如果docker pull的时候非常耗时,可能会导致这里超时.
6)配置marathon平台

  • marathon平台需要依赖mesos相关库文件和java环境所以需要安装mesos和java
    rpm -i http://repos.mesosphere.io/el/7/noarch/RPMS/mesosphere-el-repo-7-1.noarch.rpm    yum -y install mesos marathon java    
  • 需要配置环境变量
    echo "export LIBPROCESS_IP=192.168.10.124" >>  /etc/profile    source /etc/profile    #IP为本机的IP地址    bin/start --master zk://zk1.paas.smart.com:2181,zk2.paas.smart.com:2181,zk3.paas.smart.com:2181/mesos   --zk zk://zk1.paas.smart.com:2181,zk2.paas.smart.com:2181,zk3.paas.smart.com:2181/marathon --hostname 192.168.10.124 --framework_name smart    #启动服务,hostname为主机IP或本机主机名    
7)配置consul集群

  • 需要部署consul的节点,mesos-master和mesos-slave
    #将consul可执行文件放到/usr/bin目录下    mkdir /usr/local/consul/conf    #创建consul配置文件目录    cat /usr/local/consul/conf/config.conf    {    "datacenter": "diliPAAS"    ,"data_dir": "/usr/local/consul/data"    ,"log_level": "INFO"    ,"node_name": "master1"    ,"server": true    ,"retry_join":["192.168.10.210","192.168.10.184","192.168.10.152","192.168.10.136"]    ,"rejoin_after_leave":true    ,"client_addr":"192.168.10.203"    }    #retry_join IP为各个consul节点的IP,client_addr为本机IP地址,server为工作模式。    consul agent -node=master1 -server -bootstrap -data-dir=/usr/local/consul/data -dc=diliPAAS    #前台首次启动命令,需要指定consul相关数据存放目录,当启动其他consul之后可以使用如下命令启动    nohup consul agent -config-file=config.conf >consul.out&    consul members -rpc-addr=192.168.10.210:8400    #查看consul集群情况,其中rpc-addr为任意节点consul IP.     
8)nginx配置

  • 因为我们的nginx和DNS配置是通过consul-template生成配置文件的,我们先把nginx起来然后配置consul-template通过模板生成最新的配置文件。
    mkdir /data/docker/nginx/conf/    #创建nginx配置映射目录,ssl等文件也放在这个目录    docker run -d --name nginx -p 80:80  -v /data/docker/nginx/conf/nginx.conf:/etc/nginx/nginx.conf index.alauda.cn/library/nginx    #从灵雀云拉的镜像运行,如果之前拉过并push到仓库,可以从我们之前放到docker.smart.com仓库中拉去。    
9.配置consul-template来动态发现注册服务。

  • 配置nginx的服务发现和注册
cat /data/docker/nginx/conf/nginx.conf.tmpl  #nginx配置模板文件  {{$model:="test"}}  worker_processes 1;    events {  worker_connections 65535;  }    http {    #lua_code_cache off;  client_max_body_size 1G;  server_names_hash_bucket_size 64;      upstream marathon {              server 192.168.10.186:8080;              server 192.168.10.124:8080;      }        server {              server_name marathon1.paas.smart.com;              listen 80;              location / {                      proxy_pass http://marathon;              }      }        upstream registry {              server 192.168.10.200:80;      }        server {              server_name docker.smart.com;              listen 80;              location / {                      proxy_pass http://registry;              }      }
############################loop
{{range services}} {{$nameArr := .Name | split "-"}} {{if gt (len $nameArr) 1}} {{if eq $model (index $nameArr 1)}} {{$name := (index $nameArr 0)}} {{if service .Name }} upstream {{.Name}} { {{range service .Name}}     server {{.Address}}:{{.Port}};#{{range .Tags}}{{.}},{{end}}{{end}} } server {     server_name {{$name}}.smart.com;     listen 80;     location / {         proxy_pass http://{{.Name}};         proxy_set_header Host            $host;             proxy_set_header X-Forwarded-For $remote_addr;             }     } {{if eq $name  "passport"}} upstream {{.Name}}ssl { {{range service .Name}}     server {{.Address}}:{{.Port}};{{end}} } server {     server_name {{$name}}.smart.com;     listen 443;     ssl                  on;     ssl_certificate      smart_server.pem;     ssl_certificate_key  smart_server.key;     ssl_session_timeout  5m;     ssl_protocols  SSLv2 SSLv3 TLSv1 TLSv1.1 TLSv1.2;     ssl_ciphers  RC4:HIGH:!aNULL:!MD5;     ssl_prefer_server_ciphers   on;     location / {         proxy_pass http://{{.Name}}ssl;         proxy_set_header Host            $host;             proxy_set_header X-Forwarded-For $remote_addr;             }     } {{end}} {{end}} {{end}} {{end}} {{end}} } #nginx配置文件模板文件内容 mkdir -p /usr/local/consul-template/ #创建consul-template 配置文件目录 cat nginx.tmpl.conf consul="192.168.10.203:8500" template {     source="/data/docker/nginx/conf/nginx.conf.tmpl"     destination="/data/docker/nginx/conf/nginx.conf"     command="docker exec nginx nginx -s reload >> /dev/null" } #其中consul中的IP为mesos-master nohup consul-template -config=./nginx.tmpl.conf & #启动consul-template更新nginx服务 


  • 配置DNS的consul-template服务
cat /var/named/smart.com.zone.tmp  #DNS区域文件模板  {{$model:="test"}}  $TTL 1D  @       IN SOA  @ rname.invalid. (                                      0       ; serial                                      1D      ; refresh                                      1H      ; retry                                      1W      ; expire                                      3H )    ; minimum      NS      @      A       127.0.0.1      AAAA    ::1  @       IN      NS      ns.smart.com.          {{$ips:=file "/var/named/smart.json" |parseJSON}}    {{range services}}  {{$nameArr := .Name | split "-"}}  {{if gt (len $nameArr) 1}}  {{if eq $model (index $nameArr 1)}}  {{$name := (index $nameArr 0)}}  {{$name}}       IN      A        192.168.10.177  {{end}}  {{end}}  {{end}}    {{range $k,$v := $ips}}{{$kn := $k|regexReplaceAll "$" "-"|regexReplaceAll "$" $model}}{{$sk := service $kn}}{{$l:=len $sk}}{{if $l}}{{else}}  {{$k}}  IN      A       {{$v}}{{end}}{{end}}  #由于系统需要调用外部资源所以增加外部固定资源DNS A记录 smart.json  cat /var/named/smart.json  {  "ns":"192.168.10.86"    ,"zk1.paas":"192.168.10.160"  ,"zk2.paas":"192.168.10.150"  ,"zk3.paas":"192.168.10.209"  ,"master1.paas":"192.168.10.203"  ,"master2.paas":"192.168.10.210"  ,"docker":"192.168.10.200"  ,"cms":"192.168.6.54"  ,"supplier":"192.168.6.54"  ,"user":"192.168.6.54"  ,"www":"192.168.6.54"  ,"manweb":"192.168.6.54"  ,"passport":"192.168.6.54"  ,"authcode":"192.168.6.54"  ,"shop":"192.168.6.54"  ,"manage":"192.168.6.54"  ,"upload":"192.168.6.53"  ,"chat":"192.168.6.53"  ,"csc":"192.168.6.53"  ,"titan":"192.168.6.53"  ,"orders":"192.168.6.53"  ,"static":"192.168.6.53"  "smart.json" 51L, 1157C  ,"mapi.pay":"192.168.6.53"  ,"pay2":"192.168.6.53"  ,"sso":"192.168.6.54"  ,"group-admin":"192.168.6.54"  ,"group":"192.168.6.54"  ,"group-orders":"192.168.6.54"  ,"img0":"192.168.6.153"  ,"img1":"192.168.6.153"  ,"img2":"192.168.6.153"  ,"img3":"192.168.6.153"  ,"img4":"192.168.6.153"  ,"img5":"192.168.6.153"  ,"img6":"192.168.6.153"  ,"img7":"192.168.6.153"  ,"img8":"192.168.6.153"  ,"img9":"192.168.6.153"  ,"cashier.pay":"192.168.6.53"  ,"schedule":"192.168.4.14"  ,"dp":"192.168.4.211"  ,"apd":"192.168.6.54"  ,"marathon":"192.168.10.177"  ,"jenkins":"192.168.10.161"  ,"leader.paas":"192.168.10.177"  }  #添加consul-template更新DNS的配置文件  cat /usr/local/consul-template/bind.conf  consul="192.168.10.184:8500"  template {  source="/var/named/smart.com.zone.tmp"  destination="/var/named/smart.com.zone"  command="chown root.named /var/named/smart*;/etc/init.d/named restart"  }  nohup consul-template -config=./bind.conf >dns.out &  #运行DNS的consul-template服务 

10)配置Jenkins

  • jenkins服务提供拉取代码并本地编译然后推送到Docker Build服务器。
docker run -it --rm -p 80:8080  -v /data/docker/jenkins:/var/jenkins_home -v /data/docker/jenkins/maven:/home/anonymous/.m2 docker.smart.com/base/jenkins  cat /data/docker/jenkins/upload_file   #jenkins 编译完成之后推送脚本  #!/bin/bash    dcpu=${cpu:="0.4"}  dmem=${mem:="512"}  dins=${ins:="1"}  model="test"  #sysName="www"  #BUILD_NUMBER="1"  #sysDir="/data/docker/jenkins/workspace/www/diligrp-website-web/target/diligrp-website-web"  sysName=${JOB_NAME}  HOST=root@192.168.10.130  sysTmp=$(find ${WORKSPACE} -name ${filename:=*.war})  sysDir=${sysTmp/'.war'/}  echo "war path: ${sysDir}"    upload_file()  {  ssh  -nq $HOST "[ -d /tmp/${sysName} ] && rm -rf /tmp/${sysName} >>/dev/null ; mkdir -p /tmp/${sysName}"  ssh  -nq $HOST "mkdir -p /tmp/${sysName}  "  scp  -r ${sysDir}/* $HOST:/tmp/${sysName}   ssh  -nq $HOST "sh /usr/bin/Docker_Build ${sysName} ${BUILD_NUMBER} ${dcpu} ${dmem} ${dins} ${model}"  }  upload_file 

11)配置Docker Build服务

  • Docker Build其实就是将Jenkins服务器推过来的源码包利用Dockfile生成 docker 镜像。
cat /usr/bin/Docker_Build  #!/bin/bash  cpu=$3  mem=$4  ins=$5  sysName=$1  BUILD_NUMBER=$2  model=$6  #sysTmp=$(find / -name ${filename:=*.war})  #sysDir=${sysTmp/'.war'/}  sysDir=/tmp/$1  echo "war path: ${sysDir}"    if [ ! -f "${sysDir}/Dockerfile" ]; then  #дDockerfile  echo "FROM docker.smart.com/base/tomcat  RUN rm -rf /usr/local/tomcat/webapps/ROOT/*  ADD ./ /usr/local/tomcat/webapps/ROOT  " > ${sysDir}/Dockerfile  fi    dockerPath=docker.smart.com/front/$sysName:${BUILD_NUMBER}  cd ${sysDir}  docker build -t ${dockerPath} .  docker push ${dockerPath}    curl -X DELETE -H "Content-Type: application/json" http://marathon1.paas.smart.com/v2/apps/${model}/${sysName}    sleep 20    if [ ! -f "${sysDir}/app_marathon.json" ]; then  cat << EOF >${sysDir}/app_marathon.json  {"id": "/${model}/${sysName}","container": {"docker":{"image":"${dockerPath}","network": "BRIDGE","parameters":[{"key":"dns", "value": "192.168.10.86"}],"portMappings": [{"containerPort": 8080}]}},"cpus": ${cpu},"mem": ${mem},"instances": ${ins}}  EOF  fi    curl -X POST -H "Content-Type: application/json" http://marathon1.paas.smart.com/v2/apps -d@${sysDir}/app_marathon.json    sleep 5    cd /  rm -rf ${sysDir}  

平台部署遇到的一些坑:
  1. 系统防火墙问题,docker的防火墙和系统的防火墙是独立管理的,可以关闭系统防火墙。
  2. 系统selinux问题,如 果selinux设置为强制会影响docker系统权限,需改成允许或者禁用。 3. 平台VM是基于cloudstack搭建的,这里cloudstack的安全组也是一个坑,需要将相关端口开放。 4. consul第一个节点启动的时候需要带参数并指定bootstrap参数,然后在其他节点启动之后可以正常使用配置文件启动。
  3. 在部署平台之前先规划好相关主机名和DNS解析。
  4. 平台启动顺序为先启动zk然后mesos然后marathon,不然会导致marathon与mesos通讯异常。
  5. 在启动nginx时-v参数后边接nginx相关配置目录不要接文件,尤其是需要配置ssl的时候。 8.jenkins由于跑在docker里,所以在推送脚本的时候需要进入docker做下与Docker Build 的ssh信任关系。
  6. 在使用yum安装mesos的时候可能会由于linux 7下systemctl服务创建开机自动启动服务,需要disable掉。

平台使用

任务从一次Jenkins构建开始,Jenkins从代码仓库(svn,git)拉取代码并编译(配置mvn服务), 编译完成之后通过upload_file脚本将*.war包推送到Docker Build服务器,Build服务器使用Docker_Build脚本生成Dockfile文件并building成相应的app镜像推送到 docker.smart.com仓库,同时通过RESTful接口通知marathon平台,marathon平台从mesos-master请求部署 任务,mesos-master返回具体部署服务器slave信息;marathon通过slave docker Damon进程部署app任务。consul服务更新app相关IP和端口然后consul-template服务分别将IP和服务端口更新到DNS和 nginx配置文件reload服务,最后用户就可以直接访问app服务,整个任务完成。
  1. Jenkins构建app任务
     初创公司利用Docker持续集成部署
  2. marathon平台部署
     初创公司利用Docker持续集成部署
  3. 服务访问
     初创公司利用Docker持续集成部署
  • 后记:使用此平台能快速部署应用,易于扩展,从任务发布到访问服务不过短短几分钟,这在创业公司或者内部测试完全能满足需求,但是如果要落地到企业生产环境还有很多地方需要改进。
  1. Jenkins编译构建流程需要拆分,不能每次发布同一款项目都需要重新编译拉包等,效率低下且不合理。
  2. Docker的iptables网络部分需要重新设计,靠iptables的NAT转发效率有限,虽然解决了服务发现的问题。
  3. 没有比较细致的权限控制,平台安全性缺少。
  4. Docker APP应用监控以及平台相关资源监控报警缺少。(Docker监控目前可以介入监控宝)
  5. APP应用的弹性伸缩目前是人为控制,不能根据业务需求创建。
  6. 平台部署相对流程比较多,易于扩展性不足。 (后台consul-UI)
     初创公司利用Docker持续集成部署