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

jopen 4年前

这篇博客是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命令,我们需要首先创建这些文件。否则,如果这些文件不存在,构建将会失败。

 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;

你可能需要这些配置,也可能不需要。不可否认的是,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添加特定的配置文件。

 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。为了避免使用容器连接,需要执行Docker Host(应该是你的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/查看相关文档。当运行应用时,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”。

检查一切是否正常:
  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(翻译:夏彬 校对:李颖杰)

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