- 前面一篇文章,我们在Docker中搭建了Nginx服务器,并访问了Nginx服务器:javascript:void(0)
- 本文测试一个更大的Web应用程序,名为Sinatra,然后我们基于Docker对这个应用程序进行测试
- Sinatra是一个基于Ruby的Web应用框架,它包含一个Web应用库,以及简单的领域专用语言(即DSL)来构建Web应用程序。与其他复杂的Web应用框架(如Ruby on Rails)不同,Sinatra并不遵循MVC模式,而关注与让开发者创建快速、简单的Web应用
- 因此,Sinatra非常适合用来创建一个小型的示例应用进行测试。在这个例子中,我们创建一个应用程序,它接收输入的URL参数,并以JSON散列的结构输出到客户端。通过这个例子,我们也将展示一下如何将Docker容器链接起来
本文所有代码、文件链接
- 下载方式1:公众号【多栖技术控小董】回复【3673】获取免费下载链接。
- 下载方式2:Github链接https://github.com/dongyusheng/csdn-code/tree/master/Docker/sinatra。进入之后,目录内存储了文本用到的所有文件
- 上面下载完资源之后在本地创建一个sinatra目录,这个目录作为文本所有环境搭建的根目录
mkdir sinatra
二、搭建含有Sinatra服务的Docker
- 现在构建第一个镜像,使其内部安装Sinatra Web服务
第一步(创建目录)
- 在sinatra目录下创建一个webapp目录,这个目录作为Sinatra Web应用程序的根目录
cd sinatra
mkdir webapp
ls
第二步(创建源码和可执行文件)
- 在webapp目录下创建一个lib目录,然后在lib目录中创建一个app.rb文件作为Sinatra Web程序的源码
mkdir webapp/lib
touch webapp/lib/app.rb
- 在webapp目录下创建一个bin目录,然后在bin目录中创建一个webapp可执行文件,这个可执行文件就是用来启动Sinatra服务的,其源码为上面的app.rb。为了可以启动webapp,给其增加可执行权限
mkdir webapp/bin
touch webapp/bin/webapp
chmod +x webapp/bin/webapp
- 编辑app.rb文件,内容如下:
- 这个文件是Sinatra Web程序的源码
- 可以看到这个程序很简单,客户端如果以/json结尾的URL去访问sinatra Web服务(POST请求),那么服务端会将请求以JSON的格式返回给客户端
vim webapp/lib/app.rb
require "rubygems"
require "sinatra"
require "json"
class App < Sinatra::Application
set :bind, '0.0.0.0'
get '/' do
"<h1>DockerBook Test Sinatra app</h1>"
end
post '/json/?' do
params.to_json
end
end
- 编辑webapp文件,内容如下:
- 用来启动Sinatra Web程序的
- 需要可执行权限
vim webapp/bin/webapp
#!/usr/bin/ruby
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
require 'app'
App.run!
- 现在整体项目的结构如下:
第三步(创建Dockerfile)
- 在webapp目录下创建一个Dockerfile文件,待会用来构建镜像的
touch webapp/Dockerfile
- 现在整体项目的结构如下:
- 编辑Dockerfile文件,内容如下:
- FROM指令:构建该镜像的基础镜像
- MAINTAINER指令:该镜像的作者和邮箱
- ENV指令:为了重新构建缓存,详情请参阅前文的“基于构建缓存的Dockerfile模板”专题:javascript:void(0)
- 第1条RUN指令:更新源,并安装Ruby和RubyGem
- 第2条RUN指令:安装sinatra(Sinatra的库)、json(提供对JSON的支持)、redis gem(后面会用到,用来和Redis数据库进行集成)
- 第3条RUN指令:创建一个目录,作为本次Sinatra Web运行环境的根环境目录
- EXPOSE指令:公开该容器的4567端口。sinatra的默认端口为4567,所以下面启动sinatra服务的时候需要把这个端口开放出来
- CMD指令:该指令用来执行一条shell命令,此处为执行/opt/webapp/bin/目录下的webapp应用程序,也就是在镜像内运行sinatra Web应用程序
vim webapp/Dockerfile
FROM ubuntu:16.04
MAINTAINER dongyusheng "1286550014@qq.com"
ENV REFRESHED_AT 2020-07-19
RUN apt-get -yqq update && apt-get -yqq install ruby ruby-dev build-essential redis-tools
RUN gem install --no-rdoc --no-ri sinatra json redis
RUN mkdir -p /opt/webapp
EXPOSE 4567
CMD [ "/opt/webapp/bin/webapp" ]
第四步(基于Dockerfile构建镜像)
- 在sinatra目录下,输入下面的命令构建镜像:
- 镜像的名字为dongshao/sinatra
- 使用到的Dockerfile文件位于./webapp目录下
sudo docker build -t dongshao/sinatra ./webapp
- 创建之后,输入下面的命令查看我们创建的镜像:
sudo docker images dongshao/sinatra
第五步(基于创建的镜像启动Docker容器)
- 上面我们已经成功的创建镜像了,现在可以输入下面的命令来创建一个容器(备注:要在sinatra目录下执行这条命令):
- -d:将容器以守护进程的模式在宿主机中运行
- -p:将容器的4567端口映射到任意一个宿主机的端口上
- --name:将容器命名为webapp
- -v:
- 将宿主机的目录作为卷,挂在到容器中。关于“卷”的知识参阅前文:javascript:void(0)
- 此处我们将宿主机当前路径($PWD)下的webapp目录挂载到容器的/opt/webapp目录下,上面我们的Dockerfile文件已经介绍了,容器的/opt/webapp会作为sinatra Web服务的根环境目录
- 也就是说,访问/opt/webapp下的内容就会访问到宿主机当前路径下的webapp目录
- 最后一个选项为镜像的名称dongshao/sinatra
sudo docker run -d -p 4567 --name webapp -v $PWD/webapp:/opt/webapp dongshao/sinatra
- 查看容器是否创建成功:
# 查看当前系统所有的Docker容器
sudo docker ps -a
对sinatra程序进行测试
- 容器运行起来之后会执行上面的镜像,而镜像运行时会运行我们上面的那个webapp程序(根据Dockerfile中的CMD指令),而镜像的/ope/webapp/目录是映射到我们宿主机当前的webapp目录的,因此其可以去当前宿主机的webapp目录下寻找到webapp/bin/webapp的sinatra应用程序去执行
- 输入下面的命令可以检查webapp容器的日志:
sudo docker logs webapp
# 备注:加上-f选项可以与tail -f命令一样,动态更新日志的内容
sudo docker logs -f webapp
- 再查看一下webapp容器中正在运行的进程,如下所示,可以看到sinatra web服务开启正常:
sudo docker top webapp
- 现在我们可以看一下容器的4567端口映射到当前宿主机的哪个端口上,如下所示
sudo docker port webapp 4567
- 测试sinatra服务:从上图我们可以看出,容器的4567端口映射到了宿主机的32773端口上,我们就可以通过这个端口去访问容器中运行的sinatra服务了(你的宿主机IP+端口)
- 通过命令测试sinatra服务:上面我们介绍过,如果客户端的请求是以/json结尾的,那么sinatra web程序会将请求参数以JSON的形式返回给客户端,现在我们可以通过curl来测试,可以看到我们在URL中输入了nam和status,sinatra以JSON的形式返回给我们了
curl -i -H 'Accept: application/json' -d 'name=Foo&status=Bar' http://localhost:32773/json
三、搭建含有Redis服务的Docker
- 上面我们已经搭建好了一个含有Sinatra Web服务的Docker,名为dongshao/sinatra
- 现在我们需要为Sinatra Web服务加入一个Redis后端数据库,并在Redis数据库中存储客户端的URL参数
- 此处我们重新构建一个镜像,在里面只运行redis服务端,与之前的容器是相互独立的
第一步(创建目录、文件)
- 在sinatra目录下创建一个redis目录,然后再里面创建一个Dockerfile文件
mkdir redis
touch redis/Dockerfile
- Dockerfile文件的内容如下:
- FROM指令:构建该镜像的基础镜像
- MAINTAINER指令:该镜像的作者和邮箱
- ENV指令:为了重新构建缓存,详情请参阅前文的“基于构建缓存的Dockerfile模板”专题:javascript:void(0)
- RUN指令:更新源,并安装redis服务器和工具
- EXPOSE指令:公开该容器的6379端口。因为redis服务端的默认端口为6379
- ENTRYPOINT指令:与CMD类似,用来执行一条命令。此处我们为启动redis的服务端程
FROM ubuntu:16.04
MAINTAINER dongyusheng "1286550014@qq.com"
ENV REFRESHED_AT 2020-07-19
RUN apt-get -yqq update && apt-get -yqq install redis-server redis-tools
EXPOSE 6379
ENTRYPOINT ["/usr/bin/redis-server" ]
CMD []
- 现在整体的目录结构如下:
第二步(构建镜像、创建容器)
- 输入下面的命令,根据上面的Dockerfile文件构建一个名为dongshao/redis的镜像
sudo docker build -t dongshao/redis ./redis
- 创建成功之后,查看镜像,如下所示:
sudo docker images dongshao/redis
- 现在我们基于上面的镜像来创建一个Docker容器,命令如下:
- -d:将容器以守护进程的模式在宿主机中运行
- -p:将容器的6379端口映射到任意一个宿主机的端口上
- --name:将容器命名为redis
- 最后一个选项为镜像的名称
sudo docker run -d -p 6379 --name redis dongshao/redis
- 创建成功之后,查看容器,如下所示:
sudo docker ps -a
第三步(测试连接容器中的redis服务)
- 查看一下redis容器的6379端口映射到了宿主机的哪个端口,如下所示
sudo docker port redis 6379
- 从上面我们知道redis容器的6379端口映射到了宿主机的32774端口上,因此下面我们使用redis-cli客户端工具去连接,显示成功:
redis-cli -h 127.0.0.1 -p 32774
四、重新搭建一个带有redis服务的Sinatra
- 在“二”中,我们的sinatra服务端程序没有连接Redis数据库的功能,现在我们重新创建一个目录,用来存放含有redis服务的sinatra服务端程序
第一步(新建目录)
- 与“二”中的目录结构是一样的,在sinatra目录下创建一个webapp_redis,用来作为包含redis服务的sinatra web服务端根目录
mkdir webapp_redis
- 再在webapp_redis目录下创建一个bin目录,在目录内创建一个webapp文件,作为sinatra服务端程序,并赋予其可执行权限
mkdir webapp_redis/bin
touch webapp_redis/bin/webapp
chmod +x webapp_redis/bin/webapp
- 再在webapp_redis目录下创建一个lib目录,在目录内创建一个app.rb源文件,该文件为webapp的服务端源码
mkdir webapp_redis/lib
touch webapp_redis/lib/app.rb
- 编辑webapp文件,webapp的内容如下:
vim webapp_redis/bin/webapp
#!/usr/bin/ruby
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
require 'app'
App.run!
- 编辑app.rb文件,app.rb的内容如下:
- 该文件的内容与当初的app.rb文件的功能是差不多的,在之后的app.rb的基础上增加了一个连接redis数据库的功能
- sinatra服务端连接一个名为“db”的主机上的Redis数据库,端口为6379
- 然后当接收到客户端POST请求时,将URL参数保存到Redis数据库中,并在需要的时候通过GET请求从中取回这个值
vim webapp_redis/lib/app.rb
require "rubygems"
require "sinatra"
require "json"
require "redis"
class App < Sinatra::Application
redis = Redis.new(:host => 'db', :port => '6379')
set :bind, '0.0.0.0'
get '/' do
"<h1>DockerBook Test Redis-enabled Sinatra app</h1>"
end
get '/json' do
params = redis.get "params"
params.to_json
end
post '/json/?' do
redis.set "params", [params].to_json
params.to_json
end
end
第二步(创建Dockerfile)
- 在webapp_redis目录下创建一个Dockerfile文件,待会用来构建镜像的
touch webapp_redis/Dockerfile
- 现在整体项目的结构如下:
- 编辑Dockerfile文件,内容与最初那个Dockerfile文件的内容是一模一样的
- FROM指令:构建该镜像的基础镜像
- MAINTAINER指令:该镜像的作者和邮箱
- ENV指令:为了重新构建缓存,详情请参阅前文的“基于构建缓存的Dockerfile模板”专题:javascript:void(0)
- 第1条RUN指令:更新源,并安装Ruby和RubyGem
- 第2条RUN指令:安装sinatra(Sinatra的库)、json(提供对JSON的支持)、redis gem(后面会用到,用来和Redis数据库进行集成)
- 第3条RUN指令:创建一个目录,作为本次Sinatra Web运行环境的根环境目录
- EXPOSE指令:公开该容器的4567端口。sinatra的默认端口为4567,所以下面启动sinatra服务的时候需要把这个端口开放出来
- CMD指令:该指令用来执行一条shell命令,此处为执行/opt/webapp/bin/目录下的webapp应用程序,也就是在镜像内运行sinatra Web应用程序
vim webapp/Dockerfile
FROM ubuntu:16.04
MAINTAINER dongyusheng "1286550014@qq.com"
ENV REFRESHED_AT 2020-07-19
RUN apt-get -yqq update && apt-get -yqq install ruby ruby-dev build-essential redis-tools
RUN gem install --no-rdoc --no-ri sinatra json redis
RUN mkdir -p /opt/webapp
EXPOSE 4567
CMD [ "/opt/webapp/bin/webapp" ]
第三步(基于Dockerfile构建镜像)
- 在sinatra目录下,输入下面的命令构建镜像:
- 镜像的名字为dongshao/sinatra_redis
- 使用到的Dockerfile文件位于./webapp_redis目录下
sudo docker build -t dongshao/sinatra_redis ./webapp_redis
- 创建之后,输入下面的命令查看我们创建的镜像:
sudo docker images dongshao/sinatra_redis
第四步(基于创建的镜像启动Docker容器)
- 上面我们已经成功的创建镜像了,现在可以输入下面的命令来创建一个容器(备注:要在sinatra目录下执行这条命令):
- -d:将容器以守护进程的模式在宿主机中运行
- -p:将容器的4567端口映射到任意一个宿主机的端口上
- --name:将容器命名为webapp_redis
- -v:
- 将宿主机的目录作为卷,挂在到容器中。关于“卷”的知识参阅前文:javascript:void(0)
- 此处我们将宿主机当前路径($PWD)下的webapp_redis目录挂载到容器的/opt/webapp目录下,上面我们的Dockerfile文件已经介绍了,容器的/opt/webapp会作为sinatra Web服务的根环境目录
- 也就是说,访问/opt/webapp下的内容就会访问到宿主机当前路径下的webapp_redis目录
- 最后一个选项为镜像的名称
sudo docker run -d -p 4567 --name webapp_redis -v $PWD/webapp_redis:/opt/webapp dongshao/sinatra_redis
- 查看容器是否创建成功:
# 查看当前系统所有的Docker容器
sudo docker ps -a
五、将webapp_redis容器与redis容器关联通信(Docker Networking)
- 上面的“三中”我们创建了一个名为redis容器,用来提供Redis服务端。又在“四”中创建了一个名为webapp_redis的容器,用来提供sinatra web服务,并且提供连接Redis数据库的功能
- 现在我们需要将redis容器与webapp_redis容器相关联
- Docker有3种网络通信方式:
- ①Docker的内部网络
- ②Docker Networking功能(本文介绍)
- ③Docker链接(本文介绍)
- 关于这三种方式,详情请参阅:javascript:void(0),本文我们使用的Docker Networking方式
- 下面要开始新的实验了,我们将之前创建的新容器都删除(并非删除镜像),下面都重新运行新容器
# 列出当前宿主机中运行的容器
sudo docker ps
# 列出当前宿主机中所有的容器
sudo docker ps -a
# 先暂停运行的容器下面才可以删除
sudo docker stop webapp_redis redis webapp
# 删除容器
sudo docker rm webapp_redis redis webapp
# 再次查看,删除成功
sudo docker ps -a
第一步(创建新的Docker网络)
- 想要使用这种方式,需要先为Docker创建一个网络,然后在这个网络下启动容器
- 创建网络的命令如下所示:
- 此处创建了一个桥接网络,名为app
- 命令返回新创建的网络的网络ID
sudo docker network create app
- 使用下面的命令可以查看新创建的网络,如下所示:
- 可以看到这个新网络是一个本地的桥接网络(非常像docker0网络)
- 而且当前还没有容器运行在这个网络上
sudo docker network inspect app
- 备注:除了可以创建运行于单个主机之上的桥接网络,我们还可以创建一个overlay网络,overlay网络允许我们跨多台宿主机进行通信。详细文档可参阅:https://docs.docker.com/network/network-tutorial-overlay/
- 可以通过下面的命令列出的当前宿主机中所有的网络,如下所示:
sudo docker network ls
- 附加:下面的命令是用来删除一个Docker网络的
sudo docker network rm NETWORK ID
第二步(启动redis容器,并加入到app新网络)
- 上面我们已经成功的创建了一个名为app的网络,现在我们将redis容器加入到这个新网络中
- 命令如下,就是在运行的时候将容器加入app网络中:
- -d:将容器以守护进程的方式在宿主机中运行
- --net:指定了这个新容器运行在哪个网络中,此处为我们上面创建的那个app网络
- --name:容器的名字叫db
- 最后一个选项:镜像的名称
sudo docker run -d --net=app --name db dongshao/redis
- 查看新创建的容器:
sudo docker ps
- 现在我们再次输入下面的命令来查看app网络的信息,如下所示:
- 可以看到在这个网络下新增了一个容器,其有Mac、IP等地址,IP地址为172.19.0.2
sudo docker inspect app
第三步(启动webapp_redis容器,并加入到app新网络)
- 现在我们来启动webapp_redis容器,这个容器包含带有连接redis服务端的sinatra Web服务
- 在sinatra目录(注意)下,输入下面的命令启动webapp_redis容器:
- -d:将容器以守护进程的模式在宿主机中运行
- -p:将容器的4567端口映射到任意一个宿主机的端口上
- --name:将容器命名为webapp_redis
- -t:告诉Docker为要创建的容器分配一个伪tty终端。这样新创建的容器就可以提供一个交互式shell
- -i:保证容器STDIN是开启的
- -v:
- 将宿主机的目录作为卷,挂在到容器中。关于“卷”的知识参阅前文:javascript:void(0)
- 此处我们将宿主机当前路径($PWD)下的webapp_redis目录挂载到容器的/opt/webapp目录下,上面我们的Dockerfile文件已经介绍了,容器的/opt/webapp会作为sinatra Web服务的根环境目录
- 也就是说,访问/opt/webapp下的内容就会访问到宿主机当前路径下的webapp_redis目录
- dongshao/sinatra_redis:使用到的镜像的名称
- /bin/bash:容器启动一个shell
sudo docker run -p 4567 --net=app --name webapp_redis -t -i -v $PWD/webapp_redis:/opt/webapp dongshao/sinatra_redis /bin/bash
- 查看新创建的容器:
sudo docker ps
- 现在我们在宿主机中输入下面的命令来查看app网络的信息,如下所示:可以看到在这个网络下新增了一个容器,其有Mac、IP等地址,IP地址为172.19.0.3
sudo docker inspect app
- 因为我们的db容器和webapp_redis容器都运行在同一个网络之下,因此我们可以在webapp_redis容器中输入下面的命令来ping通db容器
- 上面能ping的原理是:
- Docker会将容器的名称作为域名,每一个容器对应一个域名,域名就是它们的容器名
- 因此通过ping+容器名就可以ping通容器
- 容器会把这些域名信息保存在/etc/hosts文件中,但是不知道为什么我这次配置的时候没有。正确的情况应该是该文件中有两条记录,一条为“172.19.0.2 db”,另一条为“172.19.0.2 db.app”(前面的为IP,后面的为域名,就是因为这些两个条目,我们上面才能ping通的,不知道为什么我这个文件没有)
- 上面我们sinatra Web源码中连接的Redis服务端的主机名就是“db”,就是上面我们那个名为“db”的容器
第三步(对Sinatra Web服务进行验证)
- 上面我们只是启动了sinatra_redis容器,但是没有运行里面的webapp
- 我们执行下面的命令,启动sinatra_redis容器中的Sinatra Web服务,并且最后使用“&”符号,让其在后台运行
nohup /opt/webapp/bin/webapp &
- 服务启动之后,在宿主机中查看一下sinatra_redis容器的4567端口映射到了当前宿主机的哪个端口上,如下图所示:
- sinatra服务的默认端口为4567
- 容器的4567端口映射到了宿主机的32776端口上
- 我们可以在网页中尝试访问这个Web服务,显示成功
- 可以尝试发送POST请求:
- 我们的sinatra Web服务可以接受以/json结尾的POST请求,并将客户端的请求参数存放到其连接的redis容器的redis数据库中
- 下面我们发送了一个POST请求,并发送了两个请求参数“name”和“status”
- sinatra会将POST请求的信息保存到redis数据库,并以JSON的形式再返回给客户端
curl -i -H 'Accept: application/json' -d 'name=Foo&status=Bar' http://localhost:32776/json
- 查看Redis数据库的内容:
- 因为sinatra会将数据存放到其连接的redis容器的redis数据库中
- 我们可以根据Docker的“内部网络”特性(参阅:javascript:void(0)),让宿主机直接通过私有地址连接redis容器(IP为172.19.0.2)
- 连接上之后查看里面的数据,可以看到服务端成功的保存了数据到params键中
- 我们再次发送请求,获取redis的数据:输入下面的数据,sinatra会将redis数据库中的数据返回给客户端
curl -i http://localhost:32776/json
六、使用Docker链接关联两个容器
- 除了使用Docker Networking模式关联两个容器,还可以以“Docker”链接的方式将两个容器进行关联
- 概念就不介绍了,详情请参阅:javascript:void(0)。此处介绍如何实现
第一步
- 首先将上面那两个容器先删除,我们需要创建新的容器进行测试了
# 查看当前主机中所有的Docker容器
sudo docker ps -a
# 删除前面使用到的两个容器
sudo docker rm webapp_redis db
# 再次查看,删除成功
sudo docker ps -a
第二步(创建redis容器)
- 输入下面的命令启动一个容器:
- -d:将容器以守护进程的方式在宿主机中运行
- --name:新容器的名字为redis
- dongshao/redis:容器所使用的镜像
# 启动容器
sudo docker run -d --name redis dongshao/redis
# 查看容器
sudo docker ps -a
- 该容器启动之后,其镜像的Dockerfile中配置了,会自动启动redis服务
第三步(创建webapp_redis容器,并关联redis容器)
- 输入下面的命令启动一个webapp_redis容器,命令如下:
- -p:将容器的4567端口映射到任意一个宿主机的端口上
- --name:将容器命名为webapp_redis
- --link:创建两个容器间的“客户-服务”链接。前面为链接的容器的名字(也就是我们上面的redis容器),后面的为链接的别名(取名为db)。在这个演示案例中:
- webapp容器是“客户”。redis容器是“服务”,并且为这个服务取别名为db
- 别名让我们可以一致的访问容器公开的信息,而无须关注底层容器的名字
- 链接让服务容器有能力与客户容器通信,并且能分享一些连接细节,这些细节有助于应用程序配置并使用这个链接
- -t:告诉Docker为要创建的容器分配一个伪tty终端。这样新创建的容器就可以提供一个交互式shell
- -i:保证容器STDIN是开启的
- -v:
- 将宿主机的目录作为卷,挂在到容器中。关于“卷”的知识参阅前文:javascript:void(0)
- 此处我们将宿主机当前路径($PWD)下的webapp_redis目录挂载到容器的/opt/webapp目录下,上面我们的Dockerfile文件已经介绍了,容器的/opt/webapp会作为sinatra Web服务的根环境目录
- 也就是说,访问/opt/webapp下的内容就会访问到宿主机当前路径下的webapp_redis目录
- dongshao/sinatra_redis:使用到的镜像的名称
- /bin/bash:容器启动一个shell
sudo docker run -p 4567 --name webapp_redis --link redis:db -t -i -v $PWD/webapp_redis:/opt/webapp dongshao/sinatra_redis /bin/bash
- 在宿主机中查看新创建的容器,如下所示:
sudo docker ps -a
- 备注:可以把多个容器链接在一起。比如,如果想让这个Redis实例服务于多个Web应用程序,可以把每个Web应用程序的容器和同一个redis容器链接在一起,如下所示:
sudo docker -p 4567 --name webapp2 --link redis:db
sudo docker -p 4567 --name webapp3 --link redis:db
- 提示:容器链接目前只能工作于同一台Docker宿主机中,不能链接位于不同Docker宿主机上的容器。多于多宿主机网络环境,需要使用Docker Networking,或者使用我们在后面会讨论的“Docker Swarm”,Docker Swarm可以用于完成多台宿主机上的Docker守护进程之间的编排
备注知识点(链接的安全性)
- 连接也能得到一些安全上的好处。注意,启动Redis容器时,并没有使用-p标志公开redis容器的任何端口。因为不需要这么做,通过把容器链接在一起,可以让客户容器直接访问任意服务容器的公开端口(即客户端webapp_redis容器可以连接到服务redis容器的6379端口)。更妙的是,只有使用--link标志链接到这个容器的容器才能连接到这个端口。容器的端口不需要对本地宿主机公开,现在我们已经拥有一个非常安全的模型。通过这个安全模型,就可以限制容器化应用程序被攻击面,减少应用暴露的网络
- 提示:如果用户希望,处于安全原因(或其他原因),可以强制Dcoerk只允许有链接的容器之间互相通信。为此,可以在启动Docker守护进程时加上--ice=false标志,关闭所有没有链接的容器间的通信
- 容器链接之后,会把链接信息写入以下地方:
- /etc/hosts文件
- 包含连接信息的环境变量中
第四步(查看/etc/hosts)
- 先在Docker容器中输入下面的命令来看看/etc/hosts文件,如下所示:
- 倒数第2行:为链接的redis容器的网络信息,分别为(redis容器的IP、从该连接的别名衍生的主机名db(--link选项中的别名)、redis容器的主机名(默认为容器的ID)、容器的名称)
- 最后一行:为自己的IP、自己的主机名(默认为容器的ID)
cat /etc/hosts
- 备注:上面主机的名称都是容器的ID,可以在“docker run”的时候通过-h或者--hostname选项来指定容器的主机名
- 我们的域名文件中有了链接容器的域名配置,因此可以输入下面的命令来ping通redis容器:
# 通过link别名ping
ping db
# 通过容器主机名ping
ping d827ec93ea34
# 通过容器名称ping
ping redis
# 通过IP ping
ping 172.18.0.2
- 附加知识(--add-host选项):
- 该选项用来在创建容器时,在容器的/etc/hosts文件中添加一条记录
- 例如,我们在运行容器时,想要把宿主机的IP和和域名添加到容器的/etc/hosts文件中,那么可以输入下面的命令
sudo docker run -p 4567 --add-host=docker:111.229.177.161 --name webapp2 --link redis:db ...
- /etc/hosts文件的更新:前面提到过,如果容器重启了,那么容器的IP就会变化,从Docker 1.3开始,如果被连接的容器重启了,那么/etc/hosts文件的IP地址就会自动更新
第五步(查看环境变量)
- 可以在容器中输入下面的命令来查看环境变量,如下所示:
env
- 可以看到有一些以“DB”开头的环境变量:
- Docker在连接webapp_redis和redis容器时,自动创建了这些以DB开头的环境变量。以DB开头是因为DB是创建连接时的别名
- 这些自动创建的环境变量包含以下信息:
- 子容器的名字
- 容器里运行的服务所使用的协议、IP和端口号
- 容器里运行的不同服务所指定的协议、IP和端口号
- 容器里由Docker设置的环境变量的值
- 具体的变量会因为容器的配置不同而有所不同(如容器的Dockerfile中右ENV和EXPOSE指令定义的内容)。重要的是,这些变量包含一些我们可以在应用程序中用来进行持久的容器间链接的信息
第六步(将这两个容器关联)
- 此时我们的两个容器都配置好了,现在我们可以来将两个容器进行链接了
- 有两种方法来链接容器:
- 方法一:使用环境变量里的一些连接信息
- 方法二:使用DNS和/etc/hosts信息
- 方法一:修改app.rb源文件,将其源码修改为下面的代码
- 这里使用Ruby的URI模板来解析DB_ROOT环境变量,让我们使用解析后的宿主机和端口数出来配置Redis连接信息
- 我们的应用程序现在就可以使用该连接信息来找到在以链接容器中的Redis了
- 这种抽象模式避免了我们在代码中对Redis的IP地址和端口进行硬编码,但是它仍是一种简陋的服务发现方式
require 'uri'
...
uri = URI.parse(ENV['DB_PORT'])
redis = Redis.new(:host => uri.host, :port => uri.port)
...
- 方法二:还有一种方法就是更灵活的本地DNS。修改app.rb源文件,将其源码修改为下面的代码
- 我们的应用程序会在本地查找名为db的宿主机,找到/etc/hosts文件里的相关项并解析宿主机到正确的IP地址
- 这也解决了硬编码IP地址的问题
redis = Redis.new(:host => 'db', :port => '6379')
- 附加知识(--dns、--dns-search):
- 也可以在docker run命令中加入--dns或者--dns-search标志来为某个容器单独配置DNS
- 你可以设置本地DNS解析的路径和搜索域。在https://docs.docker.com/network/上可以找到更详细的配置信息
- 如果没有这两个标志,Docker会根据宿主机的信息来配置DNS解析。可以在/etc/resolv.conf文件中查看DNS解析的配置情况
- 就是不演示,自己修改源码运行吧
- 文本我们搭建了一个简单的Web应用程序栈:
- 一个运行Sinatra的Web服务器容器
- 一个运行Redis数据库的容器
- 将这两个容器进行安全链接(使用Docker Networking模式)
- 基于这个方式,你可以轻易地扩展出任意数量的应用程序栈,并由此来管理复杂的本地开发环境,比如:
- Wordpress、HTML、CSS和JavaScript
- Ruby on Rails
- Django和Flask
- Node.js
- Play!
- 用户喜欢的其它框架
- 当然,容器间的通信方式不止一种,其它方式请参阅:javascript:void(0)。