Docker在英雄联盟游戏中的实践探索(四)
jopen
9年前
这篇博客是Riot的Docker实践系列博客的第四篇,主要讨论了如何添加一个基于Nginx的代理容器,以及如何用Compose来管理多容器应用。
背景
如果你刚加入我们,可以先从 这篇介绍的文章开始,了解我们是如何完成英雄联盟的持续发布,以及我们是如何发现这个技术栈可以很好地解决我们的问题。在我们的 第一篇文章中,我们介绍了如何把Jenkins放在Docker容器中。 第二篇文章中, 我们介绍了如何使用Docker数据卷容器来创建持久化层。我们创建了一个容器,来保存Jenkins相关的文件,以持久化插件、任务和其他的 Jenkins核心数据。我们讨论了数据卷容器和宿主机挂载卷之间的区别。最后,为了不持久化Jenkins war文件,我们介绍了如何从Jenkins主目录中移除war文件。
在第二篇文章的末尾,我们已经有了一个功能完备的、可以保存数据的Jenkins镜像。然而,由于若干原因,它还不完美。本篇文章将解决其中一个 问题:在Jenkins之前缺少一个web代理。同时,我们将运行3个容器来构建Jenkins环境。本文将分为两个部分:一是如何添加一个代理容器,二 是如何使用Compose(一种方便的Docker工具)来管理多容器应用。
读完本文,你将会完成一个全栈(full stack)的Jenkins主服务器。
第一部分: 代理容器
在Riot,我们使用Nginx作为代理,因为它可以容易地重定向至HTTPS,并使Jenkins监听在 8080端口,web服务器监听在80端口。这里不会介绍如何配置Nginx的SSL和HTTPS(互联网上可以找到文档和实例);相反,我将介绍如何在 容器中运行Nginx代理服务器,并代理Jenkins服务器。这一部分将涉及以下几点:
- 创建简单的Nginx容器
- 学习如何从本地目录中添加文件到镜像中,比如Nginx配置文件
- 使用Docker容器链接(link),连接Nginx和Jenkins
- 配置Nginx来代理Jenkins
更换OS
在Riot,我们并不常用Debian;然而,Cloudbees的Jenkins镜像使用Debian作为默认 OS,继承自Java 8镜像。但是,Docker的其中一个强大之处在于,我们可以使用任意的OS,因为宿主机并不在乎。这也展示了容器的“混合模式”。这意味着,如果应用在 多个容器之间运行,它们并不需要是同一个OS。如果某个特定的进程需要使用某个特定的Linux发行版的库或模块,这种做法就很有价值了。至于应用在 Debian/Centos/Ubuntu上的扩展性(spread)是否是个好主意,你们可以自行判断。你们可以将镜像转换成Ubuntu、Debian,或者任意一个OS。我们将使用CentOS 7。在本文的第四部分,我将会具体地讨论如何更换Jenkins镜像中的默认OS,并移除对于外部镜像的依赖性。需要考虑的是,如果你更换了OS,那么需 要更改很多命令和配置,来确保Nginx正常工作。
创建Nginx Dockerfile
在你的项目根目录中,创建一个新目录jenkins-nginx,来保存另一个Dockerfile。现在,你应该有三个目录了(如果你读过之前的文章):1、设置OS基础镜像:
FROM centos:centos7 MAINTAINER yourname
2、使用yum安装Nginx:
RUN yum -y update; yum clean all RUN yum -y install http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm; yum -y makecache RUN yum -y install nginx-1.8.0
注意我们使用的Nginx版本是1.8.0。这是一个最佳实践:总是锁定版本,避免镜像的重新构建使用未经测试的版本。
3、清除不需要的默认Nginx配置:
RUN rm /etc/nginx/conf.d/default.conf RUN rm /etc/nginx/conf.d/example_ssl.conf
4、添加配置文件:
COPY conf/jenkins.conf /etc/nginx/conf.d/jenkins.conf COPY conf/nginx.conf /etc/nginx/nginx.conf
这是我们第一次使用COPY命令。ADD命令与COPY命令十分类似。如果想透彻地了解两者的区别,我推荐你阅读以下链接:
针对我们的场景,COPY是最好的选择。就像以上文章中推荐的,我们只是拷贝单个的文件,并不需要ADD命令提供的特性(解压tarball,基于URL的获取等)。我们会更新默认的nginx.conf和Jenkins的配置文件。
5、我们希望Nginx监听80端口:
EXPOSE 80
6、启动Nginx:
CMD ["nginx"]
保存文件,但不要构建它。因为Dockerfile中有两个COPY命令,我们需要首先创建这些文件。否则,如果这些文件不存在,构建将会失败。
创建Nginx配置文件
以下的nginx.conf是一个默认配置,然后再修改特定的配置。daemon off; user nginx; worker_processes 2; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; use epoll; accept_mutex off; } http { include /etc/nginx/mime.types; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; client_max_body_size 300m; client_body_buffer_size 128k; gzip on; gzip_http_version 1.0; gzip_comp_level 6; gzip_min_length 0; gzip_buffers 16 8k; gzip_proxied any; gzip_types text/plain text/css text/xml text/javascript application/xml application/xml+rss application/javascript application/json; gzip_disable "MSIE [1-6]\."; gzip_vary on; include /etc/nginx/conf.d/*.conf; }
现在来修改默认配置:
1、使Nginx不以daemon方式运行:
daemon off;
这是因为命令行中调用nginx,Nginx将以daemon方式运行在后台。这会返回exit 0,Docker会认为进程已经退出,然后停止容器。你会发现这种现象经常发生。对于Nginx来说,只要简单地修改下配置就可以解决这个问题。
2、将Nginx的worker数目提升为2:
worker_processes 2;
这是我每次设置Nginx时必定做的事。当然,你可以选择保持该配置为1。Nginx调优可以单独写一篇文章。我不能告诉你什么是对的。粗略地说,该配置 指定了多少个单独的Nginx进程。CPU数目是一个不错的参考值,当然,很多NGINX专家会说情况远比这个要复杂。
3、事件调优(Event tuning):
use epoll; accept_mutex off;
打开epolling可以使用高效的连接模型。为了加速,我们关闭了accept_mutex,因为我们不在乎较低的连接请求数造成的资源浪费。
4、设置代理头:
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
除了关闭daemon模式之外,这是第二个必须的Jenkins代理配置。只有这样,Jenkins才能正确地处理请求,否则会出现一些警告。
5、客户端大小:
client_max_body_size 300m; client_body_buffer_size 128k;
你可能需要这些配置,也可能不需要。不可否认的是,300MB是一个很大的body大小。然而,我们的用户上传文件到Jenkins服务器,其中一些是HPI插件,一些是真实文件。
6、打开GZIP:
gzip on; gzip_http_version 1.0; gzip_comp_level 6; gzip_min_length 0; gzip_buffers 16 8k; gzip_proxied any; gzip_types text/plain text/css text/xml text/javascript application/xml application/xml+rss application/javascript application/json; gzip_disable "MSIE [1-6]\."; gzip_vary on;
为了加速,我们打开了gzip压缩。
保存文件为conf/nginx.conf。下一步就是为Jenkins添加特定的配置文件。
针对Jenkins的NGINX配置
就像上一章那样,我会先提供一份完整的配置文件,然后再修改特定的配置。你会发现大多数内容可以在Jenkins的 官方文档找到。server { listen 80; server_name ""; access_log off; location / { proxy_pass http://jenkins-master:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto http; proxy_max_temp_file_size 0; proxy_connect_timeout 150; proxy_send_timeout 100; proxy_read_timeout 100; proxy_buffer_size 8k; proxy_buffers 4 32k; proxy_busy_buffers_size 64k; proxy_temp_file_write_size 64k; } }
只有一个配置,是真正关于代理的:
proxy_pass http://jenkins-master:8080;
这个配置需要域名jenkins-master存在,这个可以通过容器连接来保证(稍后会讲到)。如果你还没使用容器连接的话,那么需要将映射Jenkins容器的IP/hostname。
然而,你不能把它设置为localhost。这是因为每个Docker容器都有自己的localhost, 将代理指向了Nginx容器本身,这里并没有运行在8080端口的Jenkins。为了避免使用容器连接,需要执行Docker Host(应该是你的Desktop或laptop)的IP地址。尽管你是知道这个信息的,但是请想象一下,如果你的Jenkins容器是运行在 Dockerhost集群中的任意一台。你需要写一个自动脚本,来获取IP地址,然后编辑配置文件。这是可以做到的,但是非常麻烦。容器连接可以简化这一 过程。
构建Nginx镜像,并连接到Jenkins镜像
现在,我们已经创建了Nginx和Jenkins的配置文件。请确保你是在顶层目录中。docker build -t myjenkinsnginx jenkins-nginx/.
构建完成之后,我们可以启动它,并连接到jenkins-master镜像,使代理发生作用。首先,请确保jenkins-data和jenkins-master正在运行。
docker run --name=jenkins-data myjenkinsdata
如果发生了错误,不用紧张,这说明容器已经存在了。这是一个好事,因为这意味着我们没有覆盖原来的数据。
docker stop jenkins-master docker rm jenkins-master docker run -p 8080:8080 -p 50000:50000 --name=jenkins-master --volumes-from=jenkins-data -d myjenkins
现在,我们终于可以启动Nginx容器,并连接jenkins-master:
docker run -p 80:80 --name=jenkins-nginx --link jenkins-master:jenkins-master -d myjenkinsnginx
请注意--link参数。你可以在 Docker官方网站上找到相关文档。需要确保域名"jenkins-master"在NGINX容器中存在,指向jenkins-master容器的内部Docker网络IP。
请注意Nginx容器必须在jenkins-master容器之后启动。这意味着,如果要停止和重启jenkins-master容器,那么也需要重启Nginx容器。
测试一切是否正常很容易。只要在浏览器中输入IP地址,一切应该正常工作了。
如果出了问题,那么一定有什么东西阻塞了80端口(这更可能发生在OSX上)。请确保防火墙已经关闭,或者至少可以接受80端口的流量。如果由于某种原因,你不能清除80端口,请关闭并删除jenkins-nginx容器,以-p 8000:80参数重启它。然后,访问 http://yourdockermachineip:8000,看看一切是否正常。
Jenkins镜像清理
我们已经让Nginx监听在80端口了,就不需要Jenkins镜像暴露8080端口了。现在我们需要删除这个端口配置。停止并重启Jenkins容器,同时Nginx容器也需要重启,因为两者是连接在一起的。每次重启时,它们都会连接在一起。docker stop jenkins-nginx docker stop jenkins-master docker rm jenkins-nginx docker rm jenkins-master docker run -p 50000:50000 --name=jenkins-master --volumes-from=jenkins-data -d myjenkins docker run -p 80:80 --name=jenkins-nginx --link jenkins-master:jenkins-master -d myjenkinsnginx
刷新浏览器 http://yourdockermachineiphere。
已经不能访问8080端口了,相反,可以通过Nginx代理访问它了。
与往常一样,代码和示例都可以在GitHub上找到,地址是 https://github.com/maxfields20 ... al_04。你会注意到makefile更新了,添加了Nginx容器和Jenkins容器的启动顺序。
Docker Compose和Jenkins
我们现在运行了3个容器,一个是Nginx代理容器,一个是Jenkins应用容器和一个保存Jenkins数据的数据卷容器。我们已经发现,因为数据卷和容器连接,这3个容器之间有启动顺序和依赖。本文将介绍Compose来处理这些。本小节将涉及:
- 使用Compose来管理多容器应用
什么是Compose
Compose是从另一个工具Fig发展而来的。Docker将它定义为“运行复杂应用的工具”。你可以从 https://docs.docker.com/compose/查看相关文档。当运行应用时,Compose可以帮我们构建镜像,决定停止和启动哪些容器。例如,如果我希望运行3个容器应用,重新构建Jenkins容器,重新运行应用-可能是升级Jenkins版本。请运行以下命令:
docker stop jenkins-nginx docker stop jenkins-master docker rm jenkins-nginx docker rm jenkins-master docker build -t myjenkins jenkins-master/. docker run --name=jenkins-master --volumes-from=jenkins-data -d myjenkins docker run -p 80:80 --name=jenkins-nginx --link jenkins-master:jenkins-master -d myjenkinsnginx
正确配置之后,我们可以运行Compose:
docker-compose stop docker-compose build docker-compose up -d
这和makefile的行为很类似。使用Compose的取舍在于,你必须额外再维护一个配置文件。
本小节单独成章,是因为使用Docker-Compose是一个个人选择。然而,如果你有很强的Windows开发背景,那么Compose可能不是一个很好的选择。
需求
- 如果你在OSX上使用Docker Toolbox, Compose是默认安装的。
- 如果你还没安装Docker Toolbox,或者使用Linux,那么请参考https://docs.docker.com/compose/install/安装Compose
- OSX 或者 Linux
请注意,Compose还不能在Windows上与Windows版本的Docker客户端一起运行。如果你使用的是Windows和 Docker Toolbox,情况会有所不同。我的建议是暂时使用makefile。Compose的开发团队正在开发Windows兼容版本,但是1.4版本尚不支 持。
第一步:创建Compos配置文件
Compose使用YAML配置文件。我们为每一个需要Compose管理的镜像添加一个条目。- 在你的项目根目录中,创建一个文件docker-compose.yml
你完全可以使用另外一个名字,但是默认情况下,Compose将首先查找这个名字。
第二步: Jenkins数据容器
编辑docker-compose.yml,添加以下内容(由于它是yaml,所以需要保持缩进):jenkinsdata: build: jenkins-data
以上是创建了一个容器的条目,叫做“jenkinsdata”。Compose不支持名字中的特殊字符,如“-”。然后,我们添加了一个构建目录,名字是Dockerfile所在的目录名,如“jenkins-data”。
检查一切是否正常:
- 保存文件。
- 执行docker-compose build。
Docker-Compose会找到jenkins-data目录,构建Dockerfile,就像执行了docker build jenkins-data/一样。你会注意到,镜像的名字是不同的。Compose使用的命名转换是“projectname_composecontainername”。默认情况下,项目名称是父目录的名字。
这种命名标准是相当重要的。这是产品环境中的容器命名方式。请确保父目录的名字是合理的,或者使用-p来制定镜像名称。你也可以使用-p来区别产品环境和开发环境。
第三步: Jenkins主镜像
继续编辑docker-compose.xml,添加以下内容:jenkinsmaster: build: jenkins-master volumes_from: - jenkinsdata ports: - “50000:50000”
就像Jenkins数据镜像一样,我们也有一个条目,来命名容器,并定义构建目录。我们也加了一条volumes_from语句,与命令行中的--volumes-from=的作用相同。但是,请注意Compose使用的容器名并不是真正的名字。这是Compose的一个方便的特性,可以让我们引用这些名字,提高可读性。Compose足够聪明,可以把它们组合在一起,来构建容器。
另一个优势是Compose知道jenkinsmaster依赖于jenkinsdata,因此会以正确的顺序来启动它们。你可以在Compose文件中以任意顺序列举它们。
最后,我们使用ports指令,来处理端口映射。为了JNLP从连接,我们需要确保Jenkins主容器做50000端口映射。
第四步:Nginx镜像
jenkinsnginx: build: jenkins-nginx ports: - "80:80" links: - jenkinsmaster:jenkins-master
将像其他两个条目,它也有一个名字(jenkinsnginx)和一个构建目录。但是,我们添加了一条links指令,就像命令行中的--link。
第五步:将所有这些组合在一些
完整的docker-compose.yml:jenkinsdata: build: jenkins-data jenkinsmaster: build: jenkins-master volumes_from: - jenkinsdata ports: - "50000:50000" jenkinsnginx: build: jenkins-nginx ports: - "80:80" links: - jenkinsmaster:jenkins-master
我们需要构建所有这些。首先,需要确保没有之前的容器的痕迹。如果你已经清理了,你可以跳过这一步:
docker stop jenkins-nginx docker rm jenkins-nginx docker stop jenkins-master docker rm jenkins-master docker rm jenkins-data
注意:迁移到新模型,我们只能丢掉数据容器,这很讨厌。在未来的文章中,我将会讨论如何备份数据。但是如果你需要备份这些数据的话,你可以先用 第三篇的docker up来备份数据。
docker-compose build docker-compose up -d
注意-d使得Docker-Compose以daemon方式运行容器,就像Docker的参数-d一样。如果你想知道哪些容器正在运行,Docker-Compose也有相类似的特性:
docker-compose ps
第六步:使用Compose维护
Compose足够聪明到了解数据卷,并持久化。- 在Jenkins实例中,创建一条测试任务
docker-compose stop
- 简单地编辑一下Jenkins主Dockerfile,例如更改MAINTAINER。
docker-compose build docker-compose up -d
- 回到Jenkins实例,查看测试任务是否已经存在。
Docker Compose会启动数据容器,并重新创建Nginx容器和主容器。
Compose有一个简单的方式来清理所有的东西:
docker-compose rm
这条命令也会删除你的数据容器。如果你不愿意删除数据容器的话,也很简单。
docker-compose rm jenkinsmaster jenkinsgninx
总结
你可以在 https://github.com/maxfields20 ... al_05上找到代码和实例。我们了解到Compose可以简化多镜像应用的管理,只需要多加一个配置文件。这个文件用来自描述容器之间的关系。
Compose是一个不错的、可操作的工具。可能的一个缺点是,容器名是基于父目录而定的,总是需要你指定一个项目名称。Compose可以使用PS和RM等工具。
我们也了解到Compose还不能在Windows上运行。你是否使用Compose取决于你是否需要Windows支持,或者你是否喜欢 Compose的命名方式。我个人很喜欢docker-compose.yml的自描述方式。你会注意到我仍然提供了makefile,因为我不需要记住 所有容器的名字。
至此,基础教程结束了。之后的文章将涉及更高级的内容。
下一步
我们还有三个高级话题:备份,构建从节点和Docker镜像的完全控制。我将会讨论如何完全制作你自己的Jenkins 镜像,而不需要依赖公共仓库。主要是因为依赖管理,或者是因为你不喜欢基于Debian的容器,更喜欢Ubuntu或者CentOS。因此,之后我们会从 头创建自己的Dockerfiles,来构建从容器。接下来的内容是:- 完全控制所有的镜像
- 备份Jenkins镜像
- 构建从容器
下次再会!
原文链接:JENKINS, DOCKER, PROXIES, AND COMPOSE(翻译:夏彬 校对:李颖杰)
来自:http://dockone.io/article/820