2 分钟阅读

简介(已过时)

前文基于docker搭建测试环境展示了docker ci环境的搭建过程,本文将进一步丰富细节,主要讲述“将web项目编译,创建镜像以及将镜像部署到某个主机上,创建容器并运行”的脚本实现。

流程

Alt text

脚本语言的选择

  1. linux shell,操作swarm和registry,使用remote api
  2. linux shell,操作swarm使用docker -H xxx command,操作registry使用remote api
  3. python脚本,操作swarm使用docker-py,操作registry使用remote api

作者一开始采用第一种方案实现了一个shell脚本,但在使用过程中发现如下问题

  1. 处理请求返回的响应值比较困难,即便在linux中安装jq等工具,代码还是比较复杂。

    • 有些请求不返回任何结果,表示正常运行
    • 有些请求返回一个id,表示正常运行
    • 有些请求返回一个json字符串,需要解析并获取其中的key值
  2. 异常处理代码非常丑陋,有时甚至无法及时发现异常(即实际运行失败,在jenkins仍显示build成功)。

使用python后,python的语言处理能力自不用多说,还可以使用try except语句块优雅的处理异常。

脚本思路

设置环境变量

脚本中用到的一些环境变量需要预先设置(jenkins需要环境变量相关插件)

DOCKER_ADDRESS=192.168.56.154:2375
REGISTRY_ADDRESS=192.168.56.154:5000
# 需要将容器的8080端口映射为主机的哪个端口
PORT=3000

一些约定

  1. web项目编译成war包后,基于REGISTRY_ADDRESS/tomcat7镜像生成REGISTRY_ADDRESS/JOB_NAME镜像。
  2. 容器在某个主机运行时,容器的name是JOB_NAME(JOB_NAME表示jenkins job的名字,可从jenkins自带的环境变量中获取)
  3. 容器对外暴漏8080和8000端口,8080在主机上的映射端口由PORT变量指定,8000端口作为调试端口,其映射端口由容器自动指定
  4. web项目在容器的/logs目录下记录日志,/logs目录映射到主机的/logs目录

脚本实现

from docker import AutoVersionClient
import os
import sys
# python 3.x
# import http.client
# python 2.x
import httplib
   
PORT = os.getenv('PORT')
JOB_NAME = os.getenv('JOB_NAME')
DOCKER_ADDRESS = os.getenv('DOCKER_ADDRESS')
REGISTRY_ADDRESS = os.getenv('REGISTRY_ADDRESS')
WORKSPACE = os.getenv('WORKSPACE')

DOCKERFILE = WORKSPACE + '/Dockerfile'
IMAGE_NAME = JOB_NAME
FULL_IMAGE_NAME=REGISTRY_ADDRESS + '/' + IMAGE_NAME

# http://docker-py.readthedocs.org/en/latest/api/#containers
jenkinsCli = AutoVersionClient(base_url='localhost:2375',timeout=10)
swarmCli = AutoVersionClient(base_url=DOCKER_ADDRESS,timeout=10)
# registryConn = http.client.HTTPConnection(REGISTRY_ADDRESS)
registryConn = httplib.HTTPConnection(REGISTRY_ADDRESS)

def remove_image_in_registry():
    try:
        registryConn.request('GET','v1/repositories/' + IMAGE_NAME + '/tags/latest')
        response = registryConn.getresponse()
        print("query image %s in registry,http status %d" %(FULL_IMAGE_NAME,response.status))
        # if image is in registry
        if response.status / 100 == 2 :
            print("registry image %s id : %s" %(FULL_IMAGE_NAME,response.read()))
            registryConn.request('DELETE','v1/repositories/'+ IMAGE_NAME + '/tags/latest')
            r = registryConn.getresponse()
            print("delete image %s in registry,http status %d" %(FULL_IMAGE_NAME,r.status))
        else :
            print("query image %s in registry fail,resason %s" %(FULL_IMAGE_NAME,response.reason))
    except Exception as inst:
        print(type(inst))    # the exception instance
        print(inst.args)     # arguments stored in .args
        print(inst)          # __str__ allows args to be printed directly
        pass
    finally:
      	registryConn.close()

def remove_container_with_imageName():
    response = swarmCli.containers(all=True)
    container_num = len(response)
    if container_num > 0 :
        i=0
        while i < container_num :  
             if response[i]['Image'] == FULL_IMAGE_NAME :
                 print('remove container :' + response[i]['Names'][0])
                 print('remove container id :' + response[i]['Id'])
                 swarmCli.remove_container(response[i]['Id'],force=True)
             i=i+1

def clear():
    # delete image in jenkins
    try:
        print("remove image : %s in jenkins" %(FULL_IMAGE_NAME))
        jenkinsCli.remove_image(FULL_IMAGE_NAME,force=True,noprune=False)
    except Exception as inst:
        print(type(inst))    # the exception instance
        print(inst.args)     # arguments stored in .args
        print(inst)          # __str__ allows args to be printed directly
        pass
    # remove image in registry
    remove_image_in_registry()
    # remove container in swarm
    remove_container_with_imageName()
    # remove image in swarm
    try:
        print("remove image : %s in swarm" %(FULL_IMAGE_NAME))
        swarmCli.remove_image(FULL_IMAGE_NAME,force=True,noprune=False)
    except Exception as inst:
        print(type(inst))    # the exception instance
        print(inst.args)     # arguments stored in .args
        print(inst)          # __str__ allows args to be printed directly
        pass
      
def build():
    # remove dockerfile
    if(os.path.isfile(DOCKERFILE)):
        os.remove(DOCKERFILE)
    file = open(DOCKERFILE,mode='a+')
    file.write("FROM %s \n"%(REGISTRY_ADDRESS + '/tomcat7'))
    file.write('ADD target/*.war $TOMCAT_HOME/webapps/ \n')
    file.write('CMD  bash start.sh')
    file.close()
    print('build dockerfile')
    response = [line for line in jenkinsCli.build(path=WORKSPACE, rm=True, tag=FULL_IMAGE_NAME)]
    response

def push():
    print("push image : %s from jenkins to registry" %(FULL_IMAGE_NAME))
    response = [line for line in jenkinsCli.push(FULL_IMAGE_NAME, stream=True)]
    response

def run():
    # pull image
    for line in swarmCli.pull(FULL_IMAGE_NAME, stream=True):
        print(line)
    # create container
    config = swarmCli.create_host_config(binds=['/logs:/logs'],port_bindings={8080:PORT,8000:None},publish_all_ports=True)
    container = swarmCli.create_container(image=FULL_IMAGE_NAME,name=IMAGE_NAME,ports=[8080,8000],volumes=['/logs'],host_config=config)
    print(container)
    # start container
    response = swarmCli.start(container=container.get('Id'))
    print(response)
def main():
  try:
      clear()
      # build dockerfile
      build()
      # push image from jenkins to registry
      push()
      # pull image,create container,start container
      run()
      sys.exit(0)
  except Exception as inst:
      print(type(inst))    # the exception instance
      print(inst.args)     # arguments stored in .args
      print(inst)          # __str__ allows args to be printed directly
      sys.exit(1)
  finally:
      jenkinsCli.close()
      swarmCli.close()
main()

回滚

基于docker ci的回滚。其实就是docker镜像保存最后一次正确的历史版本(需要将hudson build序号跟镜像的tag联系起来),一旦最新代码运行有问题,就找到最后一次正确的docker镜像,部署该镜像即可。

小结

脚本的shell版本和python版本可在git@github.com:qiankunli/dockerci.git中下载。

代码中,为了可读性加了许多注释,代码本身还是比较简单的。

作者第一次使用python写程序,有问题的地方,欢迎大家指正。

留下评论