转载

Docker在英雄联盟游戏中的实践探索(四)

Docker在英雄联盟游戏中的实践探索(四)

【编者的话】这篇博客是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命令十分类似。如果想透彻地了解两者的区别,我推荐你阅读以下链接:
      • http://stackoverflow.com/quest ... s-add
      • https://docs.docker.com/articl ... -copy
    • 针对我们的场景,COPY是最好的选择。就像以上文章中推荐的,我们只是拷贝单个的文件,并不需要ADD命令提供的特性(解压tarball,基于URL的获取等)。我们会更新默认的nginx.conf和Jenkins的配置文件。
  5. 我们希望NGINX监听在80端口:
    EXPOSE 80
  6. 启动NGINX:
    CMD ["nginx"]

保存文件,但不要构建它。因为Dockerfile中有两个COPY命令,我们需要首先创建这些文件。否则,如果这些文件不存在,构建将会失败。

Docker在英雄联盟游戏中的实践探索(四)

创建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;
    你可能需要这些配置,也可能不需要。不可否认的是,300MBs是一个很大的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添加特定的配置文件。

Docker在英雄联盟游戏中的实践探索(四)

针对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。为了避免使用容器连接,需要执行Dockerhost(应该是你的desktop或laptop)的IP地址。尽管你是知道这个信息的,但是请想象一下,如果你的Jenkins容器是运行在Dockerhost集群中的任意一台。你需要写一个自动脚本,来获取IP地址,然后编辑配置文件。这是可以做到的,但是非常麻烦。容器连接可以简化这一过程。

Docker在英雄联盟游戏中的实践探索(四)

构建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容器。

Docker在英雄联盟游戏中的实践探索(四)

测试一切是否正常很容易。只要在浏览器中输入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代理访问它了。

Docker在英雄联盟游戏中的实践探索(四)

与往常一样,代码和示例都可以在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/ 查看相关文档。当运行应用时,Compoase可以帮我们构建镜像,决定停止和启动哪些容器。

例如,如果我希望运行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不支持名字中的特殊字符,如“-”。然后,我们添加了一个“build”,名字是Dockerfile所在的目录名,如“jenkins-data”。

检查一切是否正常:

  1. 保存文件。
  2. 执行 docker-compose build
Docker在英雄联盟游戏中的实践探索(四)

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端口映射。

Docker在英雄联盟游戏中的实践探索(四)

第四步:NGINX镜像

jenkinsnginx:
build: jenkins-nginx
ports:
- "80:80"
links:
- jenkinsmaster:jenkins-master

将像其他两个条目,它也有一个名字(jenkinsnginx)和一个构建目录。但是,我们添加了一条 links 指令,就像命令行中的 --link

Docker在英雄联盟游戏中的实践探索(四)

第五步:将所有这些组合在一些

完整的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
Docker在英雄联盟游戏中的实践探索(四)

第六步:使用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
Docker在英雄联盟游戏中的实践探索(四)

总结

你可以在 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,来构建从容器。接下来的内容是:

  1. 完全控制所有的镜像
  2. 备份Jenkins镜像
  3. 构建从容器

下次再会!

原文链接: JENKINS, DOCKER, PROXIES, AND COMPOSE (翻译:夏彬 校对:李颖杰)

正文到此结束
Loading...