环境:MAC OS X 10.12.1 + Docker 1.12.1
Sinatra 是个开发框架,基于 Sinatra 开发的应用接收输入参数,然后将其转化为 JSON 输出。
1. 基于 Sinatra 框架的应用
$ cd /Users/maping/mygit/dockerbook-code/code/5/sinatra
$ cat Dockerfile
FROM ubuntu:16.04
MAINTAINER James Turnbull "james@example.com"
ENV REFRESHED_AT 2014-06-01
RUN apt-get -yqq update
RUN apt-get -y 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" ]
$ cat webapp/bin/webapp
#!/usr/bin/ruby
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
require 'app'
App.run!
$ docker build -t jamtur01/sinatra .
$ chmod +x webapp/bin/webapp
$ docker run -d -p 4567 --name webapp -v $PWD/webapp:/opt/webapp jamtur01/sinatra
注意,这里没有在 docker run 命令行上提供要运行的命令,而是定义在 Dockerfile 中。
$ docker logs -f webapp // 持续输出容器的 STDERR 和 STDOUT 里的内容
$ docker top webapp // 查看容器里正在运行的进程
$ docker port webapp 4567
访问 http://localhost:<port>
$ curl -i -H 'Accept: application/json' -d 'name=Foo&status=Bar' http://localhost:<port>/json
输出如下:
HTTP/1.1 200 OK
Content-Type: text/html;charset=utf-8
Content-Length: 29
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Server: WEBrick/1.3.1 (Ruby/2.3.1/2016-04-26)
Date: Sat, 19 Nov 2016 14:02:52 GMT
Connection: Keep-Alive
{"name":"Foo","status":"Bar"}
2. 扩展 Sinatra 应用,加入 Redis 后端数据库。
$ cd /Users/maping/mygit/dockerbook-code/code/5/sinatra/redis
$ cat Dockerfile
FROM ubuntu:16.04
MAINTAINER James Turnbull "james@example.com"
ENV REFRESHED_AT 2014-06-01
RUN apt-get -yqq update && apt-get -yqq install redis-server redis-tools
EXPOSE 6379
ENTRYPOINT ["/usr/bin/redis-server"]
CMD []
$ docker build -t jamtur01/redis .
$ docker run -d -p 6379 --name redis jamtur01/redis
$ docker port redis 6379
2.1 安装 Redis 客户端,我的 MAC 已安装,验证可以连到 Redis Server。
$ redis-cli -h 127.0.0.1 -p< port >
127.0.0.1:32769>
2.2 如何让 Sinatra 应用容器与 Redis 容器通信?
方式1:通过容器的公开端口绑定到本地网络接口。
方式2:使用 Docker 内部网络
安装 Docker 时,会创建一个新的网络接口:docker0。每个容器都会在这个接口上分配一个 IP 地址。docker0 是一个虚拟的以太网桥,用于连接容器和本地宿主网络。Docker 每创建一个容器就会创建一组互联的网络接口,一端作为容器里的 eth0 接口,另一端类似 vethec6a 这种名字,作为宿主机的一个端口。每个 veth*接口绑定到 docker0 网桥,Docker 创建了一个虚拟子网,这个子网由宿主机和所有的Docker 容器共享。
$ ip a show docker0
可以看到所有 Docker 容器的网关地址。
$ iptables -t nat -L -n
启动并进入一个容器,查看 IP 地址
$ docker run -it ubuntu bash
root@63725436b96f:/# ip a shwow eth0
root@63725436b96f:/# traceroute www.baidu.com
以上两种方式看上去都可行,但是要在应用程序里对 Redis 容器的 IP 地址硬编码;另外,如果重启容器,Docker 会改变容器的 IP 地址。
那么应该怎么办呢?
谢天谢地,Docker 有个叫做 link 的功能非常有用,它可以把多个 Docker 容器连接起来。
先删除旧的容器
$ docker stop redis
$ docker rm redis
$ docker run -d --name redis jamtur01/redis
$ docker stop webapp
$ docker rm webapp
2.3 让 Sinatra 应用容器和 Redis 容器互联
$ docker run -p 4567 --name webapp --link redis:db -it -v $PWD/webapp:/opt/webapp jamtur01/sinatra /bin/bash
说明:
(1)--name 给容器命名
(2)--link 创建两个容器之间的父子连接,一个是要连接的容器名字,另一个是连接后容器的别名。别名可以让我们访问公开的信息,而无需关注底层容器的名字。这里是 redis:db。
(3)启动 Redis 容器时,没有使用 -p 公开端口,因为不需要这样做。父容器直接访问任意子容器的公开端口。并且,只有使用 --link 连接到子容器的父容器才能连接到这个端口。子容器的端口不需要对宿主机公开,这是一个非常安全的模型。
可以让这个 Redis 实例服务于多个 Web 应用程序,如下:
$ docker run -p 4567 --name webapp2 --link redis:db -it -v $PWD/webapp:/opt/webapp jamtur01/sinatra /bin/bash
$ docker run -p 4567 --name webapp3 --link redis:db -it -v $PWD/webapp:/opt/webapp jamtur01/sinatra /bin/bash
注意,被连接的容器必须运行在同一个 Docker 宿主机上,不同宿主机上运行的容器无法连接。
Docker 在父容器的以下两个地方写入了连接信息:
(1)/etc/hosts 文件中
root@7844b851130b:/# cat /etc/hosts
输出如下:
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 db c6aaeb59594c redis // redis 容器的 IP 地址和主机名
172.17.0.3 7844b851130b // 容器自己的 IP 地址和主机名
(2)包含连接信息的环境变量中
root@7844b851130b:/# env
输出如下:
HOSTNAME=7844b851130b
DB_NAME=/webapp/db
DB_PORT_6379_TCP_PORT=6379
TERM=xterm
DB_PORT=tcp://172.17.0.2:6379
DB_PORT_6379_TCP=tcp://172.17.0.2:6379
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
DB_ENV_REFRESHED_AT=2014-06-01
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
REFRESHED_AT=2014-06-01
PWD=/
DB_PORT_6379_TCP_ADDR=172.17.0.2
DB_PORT_6379_TCP_PROTO=tcp
SHLVL=1
HOME=/root
no_proxy=localhost、*.local、169.254/16
DB_ENV_no_proxy=localhost、*.local、169.254/16
_=/usr/bin/env
Docker 在连接 webapp 和 redis 容器时,自动创建了这些以 DB 开头的环境变量。以 DB 开头是因为 DB 是创建连接时的别名。
当容器重启时,这两个地方的变量值会自动修改,这样就避免了程序硬编码的问题。
在容器里启动应用程序
root@ 7844b851130b:/# nohup /opt/webapp/bin/webapp &
在宿主机上测试应用程序
$ docker port webapp
$ curl -i -H 'Accept: application/json' -d 'name=Foo&status=Bar' http://localhost:<port>/json
输出如下:
HTTP/1.1 200 OK
Content-Type: text/html;charset=utf-8
Content-Length: 33
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Server: WEBrick/1.3.1 (Ruby/2.3.1/2016-04-26)
Date: Sat, 19 Nov 2016 14:11:56 GMT
Connection: Keep-Alive
{"name":"Foo","status":"Bar"}
访问 http://localhost:<port>
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
Sinatra 是个开发框架,基于 Sinatra 开发的应用接收输入参数,然后将其转化为 JSON 输出。
1. 基于 Sinatra 框架的应用
$ cd /Users/maping/mygit/dockerbook-code/code/5/sinatra
$ cat Dockerfile
FROM ubuntu:16.04
MAINTAINER James Turnbull "james@example.com"
ENV REFRESHED_AT 2014-06-01
RUN apt-get -yqq update
RUN apt-get -y 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" ]
$ cat webapp/bin/webapp
#!/usr/bin/ruby
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
require 'app'
App.run!
$ docker build -t jamtur01/sinatra .
$ chmod +x webapp/bin/webapp
$ docker run -d -p 4567 --name webapp -v $PWD/webapp:/opt/webapp jamtur01/sinatra
注意,这里没有在 docker run 命令行上提供要运行的命令,而是定义在 Dockerfile 中。
$ docker logs -f webapp // 持续输出容器的 STDERR 和 STDOUT 里的内容
$ docker top webapp // 查看容器里正在运行的进程
$ docker port webapp 4567
访问 http://localhost:<port>
$ curl -i -H 'Accept: application/json' -d 'name=Foo&status=Bar' http://localhost:<port>/json
输出如下:
HTTP/1.1 200 OK
Content-Type: text/html;charset=utf-8
Content-Length: 29
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Server: WEBrick/1.3.1 (Ruby/2.3.1/2016-04-26)
Date: Sat, 19 Nov 2016 14:02:52 GMT
Connection: Keep-Alive
{"name":"Foo","status":"Bar"}
2. 扩展 Sinatra 应用,加入 Redis 后端数据库。
$ cd /Users/maping/mygit/dockerbook-code/code/5/sinatra/redis
$ cat Dockerfile
FROM ubuntu:16.04
MAINTAINER James Turnbull "james@example.com"
ENV REFRESHED_AT 2014-06-01
RUN apt-get -yqq update && apt-get -yqq install redis-server redis-tools
EXPOSE 6379
ENTRYPOINT ["/usr/bin/redis-server"]
CMD []
$ docker build -t jamtur01/redis .
$ docker run -d -p 6379 --name redis jamtur01/redis
$ docker port redis 6379
2.1 安装 Redis 客户端,我的 MAC 已安装,验证可以连到 Redis Server。
$ redis-cli -h 127.0.0.1 -p
127.0.0.1:32769>
方式2:使用 Docker 内部网络
安装 Docker 时,会创建一个新的网络接口:docker0。每个容器都会在这个接口上分配一个 IP 地址。docker0 是一个虚拟的以太网桥,用于连接容器和本地宿主网络。Docker 每创建一个容器就会创建一组互联的网络接口,一端作为容器里的 eth0 接口,另一端类似 vethec6a 这种名字,作为宿主机的一个端口。每个 veth*接口绑定到 docker0 网桥,Docker 创建了一个虚拟子网,这个子网由宿主机和所有的Docker 容器共享。
$ ip a show docker0
可以看到所有 Docker 容器的网关地址。
$ iptables -t nat -L -n
启动并进入一个容器,查看 IP 地址
$ docker run -it ubuntu bash
root@63725436b96f:/# ip a shwow eth0
root@63725436b96f:/# traceroute www.baidu.com
以上两种方式看上去都可行,但是要在应用程序里对 Redis 容器的 IP 地址硬编码;另外,如果重启容器,Docker 会改变容器的 IP 地址。
那么应该怎么办呢?
谢天谢地,Docker 有个叫做 link 的功能非常有用,它可以把多个 Docker 容器连接起来。
$ docker stop redis
$ docker rm redis
$ docker run -d --name redis jamtur01/redis
$ docker stop webapp
$ docker rm webapp
$ docker run -p 4567 --name webapp --link redis:db -it -v $PWD/webapp:/opt/webapp jamtur01/sinatra /bin/bash
(2)--link 创建两个容器之间的父子连接,一个是要连接的容器名字,另一个是连接后容器的别名。别名可以让我们访问公开的信息,而无需关注底层容器的名字。这里是 redis:db。
(3)启动 Redis 容器时,没有使用 -p 公开端口,因为不需要这样做。父容器直接访问任意子容器的公开端口。并且,只有使用 --link 连接到子容器的父容器才能连接到这个端口。子容器的端口不需要对宿主机公开,这是一个非常安全的模型。
可以让这个 Redis 实例服务于多个 Web 应用程序,如下:
$ docker run -p 4567 --name webapp2 --link redis:db -it -v $PWD/webapp:/opt/webapp jamtur01/sinatra /bin/bash
$ docker run -p 4567 --name webapp3 --link redis:db -it -v $PWD/webapp:/opt/webapp jamtur01/sinatra /bin/bash
注意,被连接的容器必须运行在同一个 Docker 宿主机上,不同宿主机上运行的容器无法连接。
Docker 在父容器的以下两个地方写入了连接信息:
(1)/etc/hosts 文件中
root@7844b851130b:/# cat /etc/hosts
输出如下:
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 db c6aaeb59594c redis // redis 容器的 IP 地址和主机名
172.17.0.3 7844b851130b // 容器自己的 IP 地址和主机名
(2)包含连接信息的环境变量中
root@7844b851130b:/# env
HOSTNAME=7844b851130b
DB_NAME=/webapp/db
DB_PORT_6379_TCP_PORT=6379
TERM=xterm
DB_PORT=tcp://172.17.0.2:6379
DB_PORT_6379_TCP=tcp://172.17.0.2:6379
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
DB_ENV_REFRESHED_AT=2014-06-01
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
REFRESHED_AT=2014-06-01
PWD=/
DB_PORT_6379_TCP_ADDR=172.17.0.2
DB_PORT_6379_TCP_PROTO=tcp
SHLVL=1
HOME=/root
no_proxy=localhost、*.local、169.254/16
DB_ENV_no_proxy=localhost、*.local、169.254/16
_=/usr/bin/env
Docker 在连接 webapp 和 redis 容器时,自动创建了这些以 DB 开头的环境变量。以 DB 开头是因为 DB 是创建连接时的别名。
当容器重启时,这两个地方的变量值会自动修改,这样就避免了程序硬编码的问题。
root@
$ curl -i -H 'Accept: application/json' -d 'name=Foo&status=Bar' http://localhost:<port>/json
Content-Type: text/html;charset=utf-8
Content-Length: 33
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Server: WEBrick/1.3.1 (Ruby/2.3.1/2016-04-26)
Date: Sat, 19 Nov 2016 14:11:56 GMT
Connection: Keep-Alive
{"name":"Foo","status":"Bar"}
访问 http://localhost:<port>
2.4 程序是如何连接 Redis 的?
$ cat 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
应用程序会在本地查找名叫 db 的主机,找到 /etc/hosts 文件里的项并正确解析。
这种方式称为使用本地 DNS 方法,注意这里硬编码了 redis 端口。
还有一种方法是读取环境变量。
require "uri"
......
uri = URI.parse(ENV['DB_PORT'])
redis = Redis.new(:host => uri.host, :port => uri.port)
......
没有评论:
发表评论