导语
现在很火的一个技术就是云计算服务,除了很早就开始做云计算的Amazon,阿里、腾讯、京东、百度等巨头也分分加入云计算的领域中。谈到云计算,就离不开Docker这个方便的工具啦。正如Docker的slogan所说”Build, Ship, and Run Any App, Anywhere”,它通过对应用组件的封装(Packaging)、分发(Distribution)、部署(Deployment)、运行(Runtime)等生命周期的管理,达到应用组件级别的”一次封装,到处运行”。
今天我就来学习一下什么是Docker,以及如何在阿里云服务器上用Docker上进行持续集成/持续部署(CI/CD)的,并把学习和实践的过程整理记录下来,其中某些概念性的知识可能会直接摘录官方文档或者相关教程,希望在自己之后的项目之中,可以派的上用场。
如果你已经熟悉了Docker,并会使用它了,那么请直接跳到本文的后半部分 CI/CD on Docker
Docker 简介
什么是Docker?
Docker它的英文本意是码头工人,也就是搬运工,这种搬运工搬运的是集装箱(Container),集装箱里面装的可不是商品货物,而是任意类型的App,Docker把App(叫Payload)装在Container内,通过Linux Container技术的包装将App变成一种标准化的、可移植的、自管理的组件,这种组件可以在你的latop上开发、调试、运行,最终非常方便和一致地运行在production环境下。
Docker为App提供了一种自动化构建机制(Dockerfile),包括打包,基础设施依赖管理和安装等等;相比于传统的虚拟机技术,它不需要完整的虚拟出一个操作系统,可以降低资源的消耗,提高服务器使用效率,通过其构建工具,又可以帮助我们快速的部署运行应用。
为什么是Docker?
为什么要用Docker一文介绍了Docker的优势,以及我们为什么要使用它。大概有以下几点:
- 更高效地利用系统资源
- 更快速的启动时间
- 一致的运行环境
- 持续交付和部署
- 更轻松的迁移
- 更轻松的维护和拓展
其中”持续交付和部署”是这篇博客所要重点关注的内容。
对开发和运维(DevOps)人员来说,最希望的就是一次创建或配置,可以在任意地方正常运行。
使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。开发人员可以通过 Dockerfile 来进行镜像构建,并结合持续集成(Continuous Integration) 系统进行集成测试,而运维人员则可以直接在生产环境中快速部署该镜像,甚至结合持续部署(Continuous Delivery/Deployment) 系统进行自动部署。
而且使用 Dockerfile 使镜像构建透明化,不仅仅开发团队可以理解应用运行环境,也方便运维团队理解应用运行所需条件,帮助更好的生产环境中部署该镜像。
Docker 基本概念
在使用Docker之前,有几个最基本的概念我们需要了解。
Docker 镜像
Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
Docker 容器
镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。
Docker 仓库
镜像构建完成后,可以很容易的在当前宿主上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。
PS: 由于这篇博客暂时不会涉及到在不同服务器运行同一个Docker,所以关于Docker仓库的内容就会先简略跳过。
Docker 安装
我将在阿里云服务器上完成Docker的安装和后续的实践。服务器为Ubuntu 16.04 LTS
Docker 官方为了简化安装流程,提供了安装一套安装脚本:
1 | curl -sSL https://get.docker.com/ | sh |
由于墙内原因,下载可能会出现错误,这里就直接使用了阿里云提供的安装脚本进行安装
PS: 该脚本仅支持阿里云服务器内网访问,非内网用户使用不了。但是可以使用中国区优化的脚本:github
1 | curl -sSL http://acs-public-mirror.oss-cn-hangzhou.aliyuncs.com/docker-engine/intranet | sh - |
运行完脚本之后,发现Docker引擎已经完成安装,并且启动了。
可以通过下面的命令,查看docker引擎运行的状态:
1 | systemctl status docker |
1 | docker version # 查看docker 版本 |
2 | |
3 | Client: |
4 | Version: 17.05.0-ce |
5 | API version: 1.29 |
6 | Go version: go1.7.5 |
7 | Git commit: 89658be |
8 | Built: Thu May 4 22:10:54 2017 |
9 | OS/Arch: linux/amd64 |
10 | |
11 | Server: |
12 | Version: 17.05.0-ce |
13 | API version: 1.29 (minimum version 1.12) |
14 | Go version: go1.7.5 |
15 | Git commit: 89658be |
16 | Built: Thu May 4 22:10:54 2017 |
17 | OS/Arch: linux/amd64 |
18 | Experimental: false |
19 | |
20 | docker run --rm hello-world # 运行hello-world |
21 | |
22 | Unable to find image 'hello-world:latest' locally |
23 | |
24 | latest: Pulling from library/hello-world |
25 | 78445dd45222: Pulling fs layer |
26 | |
27 | 78445dd45222: Pull complete |
28 | Digest: sha256:c5515758d4c5e1e838e9cd307f6c6a0d620b5e07e6f927b07d05f6d12a1ac8d7 |
29 | Status: Downloaded newer image for hello-world:latest |
30 | |
31 | Hello from Docker! |
32 | This message shows that your installation appears to be working correctly. |
33 | |
34 | To generate this message, Docker took the following steps: |
35 | 1. The Docker client contacted the Docker daemon. |
36 | 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. |
37 | 3. The Docker daemon created a new container from that image which runs the |
38 | executable that produces the output you are currently reading. |
39 | 4. The Docker daemon streamed that output to the Docker client, which sent it |
40 | to your terminal. |
41 | |
42 | To try something more ambitious, you can run an Ubuntu container with: |
43 | docker run -it ubuntu bash |
44 | |
45 | Share images, automate workflows, and more with a free Docker ID: |
46 | https://cloud.docker.com/ |
47 | |
48 | For more examples and ideas, visit: |
49 | https://docs.docker.com/engine/userguide/ |
使用镜像
获取镜像
Docker Hub上有大量的高质量的镜像可以用,这里我们就说一下怎么获取这些镜像并运行。
从 Docker Registry 获取镜像的命令是 docker pull。其命令格式为:
1 | docker pull [选项] [Docker Registry地址]<仓库名>:<标签> |
运行镜像
docker run 就是运行容器的命令
-it
:这是两个参数,一个是-i
:交互式操作,一个是-t
终端。我们这里打算进入bash
执行一些命令并查看返回结果,因此我们需要交互式终端。--rm
:这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动docker rm
。我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用--rm
可以避免浪费空间。ubuntu:14.04
:这是指用ubuntu:14.04
镜像为基础来启动容器。bash
:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是bash
。
进入容器后,我们可以在 Shell 下操作,执行任何所需的命令。这里,我们执行了 cat /etc/os-release
,这是 Linux 常用的查看当前系统版本的命令,从返回的结果可以看到容器内是 Ubuntu 14.04.5 LTS
系统。
最后我们通过 exit
退出了这个容器。
列出镜像
要想列出已经下载下来的镜像,可以使用 docker images
命令。
1 | docker images |
2 | REPOSITORY TAG IMAGE ID CREATED SIZE |
3 | hello-world latest 48b5124b2768 4 months ago 1.84kB |
CI/CD on Docker
什么是CI/CD?
持续集成(Continuous Integration)简称CI,持续集成强调开发人员提交了新代码之后,立刻进行构建、(单元)测试。根据测试结果,我们可以确定新代码和原有代码能否正确地集成在一起。
持续部署(Continuous Delivery/Deployment)简称CD,持续部署则是在持续交付的基础上,把部署到生产环境的过程自动化。
从The Product Managers’ Guide to Continuous Delivery and DevOps一文中对持续集成、持续交付、持续部署三个概念的解释,我们大概可以了解到三者是递进关系的,后者再前者的基础之上进一步自动化。本文只做一个简单的demo,以后根据业务场景的不同再进行更改。
Travis CI
在之前我所写的博文GitHub+Travis CI 持续集成 一文中,介绍了如何使用GitHub和Travis CI 进行持续集成。使用Travis CI也可以在CI过程中使用Docker来进行部署、运行、测试。只需要在.travis.yml
中添加简单的配置即可
1 | sudo: required |
2 | |
3 | services: |
4 | - docker |
然后在before_install
标签下,添加相关的docker语句即可。由于Travis CI 使用的是在线的服务,所以不需要自己再另外安装Docker,如果是自己在服务器上私有的Travis CI才需要。给人感觉整体的配置流程是比较简单,方便的。具体可以参考官方文档Using Docker in Builds。 与配置完Jenkins之后的持续部署类似,本文不再详细叙述,有需要的话再进行学。
Jenkins
在玩过了Travis CI之后,这次再尝试学习一下另一款持续集成工具Jenkins,并学习、记录下如何使用Jenkins和Docker做持续集成。这里与Travis CI不同,是直接使用自己的服务器来进行持续集成的,可以实现更多的定制,以及闭源代码的持续集成。(Travis CI在线服务只免费提供给GitHub开源代码)。
什么是Jenkins
jenkins是一个广泛用于持续构建的可视化web工具,持续构建说得更直白点,就是各种项目的”自动化”编译、打包、分发部署。jenkins可以很好的支持各种语言(比如:java, c#, php等)的项目构建,也完全兼容ant、maven、gradle等多种第三方构建工具,同时跟svn、git能无缝集成,也支持直接与知名源代码托管网站,比如github、bitbucket直接集成。
通过Docker安装和启动Jenkins
安装
Jenkins需要Java环境,有了Docker这个利器,我们就省去了安装Java环境的麻烦,只需执行如下命令即可。通过执行docker pull 指令,我们可以很方便地获取到可以用来执行jenkins的docker镜像。
1 | docker pull jenkins:latest |
通常的做法是把jenkins文件存储地址挂载在宿主机上,这样子如果jenkins服务器重装或者迁移就可以方便地迁移项目配置了。
1 | docker run -d --name myjenkins -p 8080:8080 -v ${pwd}/data:/var/jenkins_home jenkins |
通过这种方法来启动有时候会不成功,可能是因为路径权限存在着问题。这里使用https://github.com/AliyunContainerService/docker-jenkins版本的修改版jenkins来进行构建自己的Jenkins镜像
1 | git clone https://github.com/AliyunContainerService/docker-jenkins |
2 | cd docker-jenkins/jenkins |
3 | docker build -t myjenkins . |
启动
然后基于新镜像启动Jenkins容器
1 | docker run -d -p 0.0.0.0:8080:8080 -p 50000:50000 -v $(pwd)/data:/var/jenkins_home --name jenkins myjenkins |
查看正在运行的docker 容器
1 | docker ps |
2 | |
3 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES |
4 | 10ea38c68ec5 myjenkins "/entrypoint.sh" 2 minutes ago Up 2 minutes 0.0.0.0:8080->8080/tcp, 0.0.0.0:50000->50000/tcp jenkins |
PS: $(pwd)
是当前路径
基于myjenkins镜像构建了jenkins容器,将docker 8080端口(冒号后)与宿主机的8080端口(冒号前)绑定在一起,可以通过宿主机ip+8080端口号来访问jenkins页面,访问50000端口可以获取到端口信息。
配置环境变量
jenkins->系统管理->管理节点->宿主机
配置所需要的环境变量,则jenkins就可以使用到宿主机上的一些环境了。注意JAVA_HOME
的配置要求的是JDK的位置而非JRE的位置。
安装插件
在data/serects下查看initialAdminPassword进入Jenkins控制页面
安装建议的插件
开始界面
对大作业进行自动构建
上篇博客中,完成了对大作业一个java项目的maven自动构建,这次就通过jenkins和docker来实现。
配置maven
在系统管理->管理插件->可选插件 中安装Maven Integration plugin
这个插件
在系统管理->Global Tool Configuration 选择maven路径,或者自动安装
GitHub 插件
此外,在jenkins我们要需要安装一些关于GitHub的插件,来支持Webhook
新建 Jenkins 项目
这里选用自由风格的软件项目来构建
选择源码地址,需要添加GitHub账号权限,可以使用ssh key也可以使用账号密码
可以通过脚本触发,这里使用了GitHub Plugin所提供的GitHub hook trigger for GITScm polling,当GitHub上的项目仓库发生改变之后会发出相应的请求。
其中maven版本选择刚刚所设置的,执行clean和install目标。
构建
点击立即构建,就可以完成构建了。红色表示失败,蓝色表示成功,点击进去可以查看详细。
GitHub 自动触发
在仓库的settings->services中添加jenkins配置,根据提示添加触发url即可。url地址为http://your_jenkins_url/github-webhook/ 。这样子当GitHub仓库发生更新时就会触发自动构建功能了。
自动部署
构建中,可以执行shell指令,如果在后期大作业完成前端与后端之后,就可以直接在构建中,创建一个docker镜像、容器并且运行这个容器,使得我们可以直接访问到部署完成的网页了。这个会在后续继续改进。
PS:这次是直接在宿主机上进行配置,此外Jenkins还可以配置另外的主机节点来运行maven等构建,同时也可以指定一些环境,因为每一次都重新下载maven的话其实是比较花时间的。大致的流程就可以像下图所示一样。
总结
在了解了Docker之后,发现这是一个比虚拟机更加强大的工具,速度也很快,方便我们快速部署应用。在Docker上实现CI/CD可以让我们只需要提交GitHub上的代码更改之后,便可以在网页上看到产品的新版本了。大量地减轻了这些重复性的工作。
这次也了解了Jenkins,相比于Travis CI的话,配置会更加麻烦一些,整体的流程都是相近的,但是可以定制更多自己想要的功能,在后续过程中如果与Docker结合,可以看到一个产品的完整生命周期,自动化完成的过程。因为还没有完成大作业的前后端全部功能,所以看具体的效果可以参考Gitlab+Jenkins+docker完成Maven项目的自动部署一文。
参考
The Product Managers’ Guide to Continuous Delivery and DevOps