nginx-proxy 自动反向代理

docker 使得部署应用非常的方便,真正的让在单机部署多个应用成为一键化操作。然而,如果单机中有多个应用需要对外提供服务,用 docker 有时候感觉并不是那么优雅。

如果在单机中需要部署多个应用,而这些应用需要占用同一个端口,比如多个 web 应用同时需要 80/443 端口,或者希望对于来自同一个域名的流量进行 load balance,希望这些流量可以被分发到不同的容器上。这些常见的场景,目前操作起来并不方便。

对于上述场景,常见的做法就是在本地部署一个 nginx,每当有 docker 容器更新的时候,便修改 nginx 的配置文件,使满足需求。

方法很简单,但是繁琐,如果有工具可以把这些操作自动化,那么在使用 docker 的时候,是不是可以节省很多精力。在 daocloud hub 里,的确有这么一样工具,即是 nginx-proxy。这是一个会自动获取容器配置并做好反向代理和负责均衡的 nginx。

部署 nginx-proxy

首先需要把共享使用的端口预留给 proxy,比如 80、443 端口,这样,就可以让打在这两个端口上的流量由 proxy 来分配。其次,因为 proxy 需要获取 docker 容器的配置进行对 nginx 的配置文件修改,因此,需要把 docker.sock 映射到容器中。

docker 启动命令

docker run -d -p 80:80 -v /var/run/docker.sock:/tmp/docker.sock:ro daocloud.io/daocloud/nginx-proxy

建议使用 compose 来配置,yml文件如下:

proxy:
  image: daocloud.io/daocloud/nginx-proxy:0.3.6
  privileged: false
  restart: always
  ports:
  - 443:443
  - 80:80
  volumes:
  - /var/run/docker.sock:/tmp/docker.sock:ro

反向代理

对于配置好 proxy 的主机,每当在主机上启动一个容器的时候,proxy 都会自动检查该容器是否含有 VIRTUAL_HOST 这个环境变量,当检查到某个刚启动的容器含有这个环境变量时,便会自动修改配置文件,使得这个来自 环境变量描述的域名 的流量会被打在这个容器上。

那么,在启动容器的时候,只需要添加一个环境变量,比如:

hypo_blog:
  image: daocloud.io/cmss/hypo-blog:master-796038f
  privileged: false
  restart: always
  ports:
  - '4000'
  environment:
  - VIRTUAL_HOST=blog.ihypo.net

负载均衡

当有多个容器含有相同的 VIRTUAL_HOST 的时候,proxy 便会自动做好负载均衡,如果使用 daocloud 的话:

只需要在容器界面调整容器个数,proxy 会自动做好 load balance。

如果进入 proxy 容器中查看的话,也可以看到生成的配置文件:

upstream blog.ihypo.net {
		   # dao_hypo_blog_2
		   server 192.168.0.6:4000;
		   # dao_hypo_blog_1
		   server 192.168.0.4:4000;
}

其他

nginx-proxy 的使用非常简单,还支持多域名,多端口,https 等,可以参考镜像的 README

参考

https://github.com/jwilder/nginx-proxy

http://jasonwilder.com/blog/2014/03/25/automated-nginx-reverse-proxy-for-docker/

2016/08/19 12:28 pm posted in  Docker

Dockerfile基本结构与指令

Dockerfile是一个文本格式的配置文件,用户可以使用Dockerfile快速创建自定义的镜像。

基本结构

Dockerfirl由一行行命令语句组成,并且支持以#开头的注释行。 一般而言,Dockerfile分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。例如:

# This dockerfile uses the ubuntu image
# VERSION 2 - EDITION 1
# Author: docker_user
# Command format: Instruction [arguments / command] ..

# Base image to use, this must be set as the first line
FROM ubuntu

# Maintainer: docker_user  (@docker_user)
MAINTAINER docker_user docker_user@email.com

# Commands to update the image
RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y nginx
RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf

# Commands when creating a new container
CMD /usr/sbin/nginx

其中,一开始必须指明所基于的镜像名称,接下来推荐说明维护者信息。 后面则是镜像操作指令,例如RUN指令,RUN指令将对镜像执行跟随的命令。每运行一条 RUN 指令,镜像添加新的一层,并提交。最后是 CMD指令,来指定运行容器时的操作命令。

指令

指令的一般格式为:INSTRUCTION arguments,指令包括 FROM、MAINTAINER、RUN 等。

FROM

格式为 FROM 或FROM :。 第一条指令必须为 FROM 指令。并且,如果在同一个Dockerfile中创建多个镜像时,可以使用多个 FROM 指令(每个镜像一次)。

MAINTAINER

格式为 MAINTAINER ,指定维护者信息。

RUN

格式为 RUN 或RUN ["executable", "param1", "param2"]。 前者将在 shell 终端中运行命令,即 /bin/sh -c;后者则使用 exec 执行。指定使用其它终端可以通过第二种方式实现,例如RUN ["/bin/bash", "-c", "echo hello"]。 每条 RUN指令将在当前镜像基础上执行指定命令,并提交为新的镜像。当命令较长时可以使用 \ 来换行。

CMD

支持三种格式

  • CMD ["executable","param1","param2"] 使用 exec 执行,推荐方式;
  • CMD command param1 param2 在 /bin/sh 中执行,提供给需要交互的应用;
  • CMD ["param1","param2"] 提供给 ENTRYPOINT 的默认参数;

指定启动容器时执行的命令,每个 Dockerfile 只能有一条 CMD命令。如果指定了多条命令,只有最后一条会被执行。 如果用户启动容器时候指定了运行的命令,则会覆盖掉 CMD指定的命令。

EXPOSE

格式为 EXPOSE [...]。
告诉 Docker 服务端容器暴露的端口号,供互联系统使用。在启动容器时需要通过 -P,Docker 主机会自动分配一个端口转发到指定的端口。

ENV

格式为 ENV
指定一个环境变量,会被后续 RUN 指令使用,并在容器运行时保持。 例如

ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH

ADD

格式为 ADD
该命令将复制指定的 到容器中的 。 其中 可以是Dockerfile所在目录的一个相对路径;也可以是一个 URL;还可以是一个 tar 文件(自动解压为目录)。

COPY

格式为 COPY
复制本地主机的 (为 Dockerfile 所在目录的相对路径)到容器中的
当使用本地目录为源目录时,推荐使用 COPY。

ENTRYPOINT

两种格式:

  • ENTRYPOINT ["executable", "param1", "param2"]
  • ENTRYPOINT command param1 param2(shell中执行)。

配置容器启动后执行的命令,并且不可被 docker run 提供的参数覆盖。 每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个时,只有最后一个起效。

VOLUME

格式为 VOLUME ["/data"]。 创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。

USER

格式为 USER daemon。 指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户。 当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户,例如:RUN groupadd -r postgres && useradd -r -g postgres postgres。要临时获取管理员权限可以使用 gosu,而不推荐 sudo。

WORKDIR

格式为 WORKDIR /path/to/workdir。 为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录。 可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。例如

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

则最终路径为 /a/b/c。

ONBUILD

格式为 ONBUILD [INSTRUCTION]。 配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。 例如,Dockerfile 使用如下的内容创建了镜像 image-A。

[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]

如果基于 image-A 创建新的镜像时,新的Dockerfile中使用 FROM image-A指定基础镜像时,会自动执行 ONBUILD指令内容,等价于在后面添加了两条指令。

FROM image-A

#Automatically run the following
ADD . /app/src
RUN /usr/local/bin/python-build --dir /app/src

使用 ONBUILD指令的镜像,推荐在标签中注明,例如 ruby:1.9-onbuild。

2015/06/07 10:27 am posted in  Docker

Docker网络基础

大量的互联网应用服务包括多个服务组件,这往往需要多个容器之间通过网络通信进行相互配合。 Docker目前提供了映射容器端口到宿主主机和容器互联机制来为容器提供网络服务。

端口映射实现访问容器

在启动容器的时候,如果不指定对应参数,在容器外部是无法通过网络来访问容器内的网络应用和服务的。

从外部访问容器应用

当容器中运行一些网络应用,要让外部访问这些应用时,可以通过-P或-p参数来指定端口映射。当使用-P标记时,Docker会随即映射49000~49900中的端口至容器内部开放的网络端口。 小写-p则可以指定要映射的端口,并且,在一个指定端口上只可以帮顶一个容器。支持的格式有:

  • ip:hostPort:containerPort
  • ip::containerPort
  • hostPort:containerPort

映射所有接口地址

使用hostPort:containerPort格式将本地5000端口映射到容器5000端口(之前创建私有仓库的例子):

docker run -d -p 5000:5000 registry

这时默认会帮顶本地所有接口上的所有地址。可以多次使用-p参数从而映射多个端口。

映射到指定地址的指定端口

可以使用ip:hostPort:containerPort格式指定映射使用一个特定地址,比如locakhost地址127.0.0.1。

docker run -d -p 127.0.0.1:5000:5000 registry

映射到指定地址的任意端口

可以使用ip::containerPort格式帮顶localhost的任意端口到容器的5000端口,本地主机会自动分配一个端口:

docker run -d -p 127.0.0.1::5000 registry

可以使用udp标记来指定udp端口:

docker run -d -p 127.0.0.1:5000:5000/udp registry

查看端口映射配置

可以使用docker port命令来查看当前映射的端口配置,也可以查看绑定的地址:

docker port registry 
5000/tcp -> 0.0.0.0:5000
docker port registry 5000
0.0.0.0:5000

容器有自己的内部网络和IP地址(使用docker inspect + ID可以获取所有变量值)。

容器互联实现容器间通信

容器的连接(linking)系统是除了端口映射外另一种可以与容器中应用进行交互的方式。他会在源和接收容器之间创建一个隧道,接受容器可以看到源容器指定的信息。

容器互联

使用--link参数可以让容器之间安全的进行交互。 比如创建一个数据库容器:

docker run --name dbserver mysql

让后创建一个容器,并将这个容器连接到dbserver容器:

docker run -d --name likeweb --link dbserver:dbserver ubuntu

因为要连接的容器并没有启动,所以建立完容器之后会报错,这里只是演示--link,请忽略。 --link参数的格式为--link name:alias,其中name是要链接的容器的名称,alias是这个连接的别名。 可以用docker ps 命令查看连接情况。 这样Docker两个容器之间创建了一个安全隧道而不需要开放外部端口,避免了数据库端口到外部网络上。 Docker通过两种方式为容器公开连接信息:

  • 环境变量
  • 更新/etc/hosts文件

可以使用env命令来查看容器的环境变量。 Docker目前采用了Linux系统自带的网络系统来实现对网络服务的支持,这既可以利用现有成熟的技术提供稳定的支持,又可以实现快速的高性能转发。

2015/06/06 10:27 am posted in  Docker

Docker数据管理

用户在使用Docker的过程中,往往需要能查看容器内应用产生的数据,或者需要把容器内的数据进行备份,甚至多个容器之间进行数据共享,这必然涉及到Docker的数据管理。 容器中管理数据主要有两种方式:

  • 数据卷(Data Volumes)
  • 数据卷容器(Data Volumes Dontainers)

数据卷

数据卷是一个可供容器使用的特殊目录,他绕过文件系统,可以提供很多有用的特性:

  • 数据卷可以在容器之间共享和重用
  • 对数据卷的修改会立马生效
  • 对数据卷的更新,不会影响到镜像
  • 卷会一直存在,直到没有容器使用

数据卷的使用,类似于Linux下对目录或文件进行mount操作。

创建一个数据卷

在使用docker run命令的时候,使用-v参数可以在容器内创造一个数据卷,如果多次使用-v参数可以创造多个数据卷。 比如创建一个私有仓库的时候:

docker run -d -p 5000:5000 --name registry -v /tmp/registry registry

创建一个/tmp/registry数据卷挂在到这个容器里。

挂在一个主机目录作为数据卷

使用-v参数也可以制定挂在一个本地已有的目录到容器中作为数据卷,还是举创建私有仓库的例子:

docker run -d -p 5000:5000 --name registry -v /data/registry:/tmp/registry registry

这样便是把本地的/data/registry挂在到容器中作为/tmp/registry。不过本地目录一定要是绝对路径,如果,目录不存在Docker会自动创建。 另外,Docker挂在数据卷的默认权限是读写(rw),也可以使用(ro)指定为只读权限:

docker run -d -p 5000:5000 --name registry -v /data/registry:/tmp/registry:ro registry

这样容器就没有写入的权限了。

挂在一个主机文件作为数据卷

也是可以把主机的一个文件挂在到容器中作为数据卷,使用命令和挂在目录一样。 不过直接挂在一个文件到容器中,又在外部编辑文件有可能导致文件inode信息的改变,从Docker1.1.0起,这回导致错误信息。 所以建议挂在目录以达到访问文件的目的。

数据卷容器

如果需要在容器之间共享一些持续的更新的数据,最简单的方式是使用数据卷容器。数据卷容器其实就是一个普通的容器,专门用它提供数据卷提供其他容器挂载。使用方法如下: 首先,创建一个数据卷容器比如dbdata,并在其中创建一个数据卷并挂在到/dbdata。

docker run -it -v /dbdata --name dbdata ubuntu

然后可以在其他容器使用--volumes-from来挂载dbdata容器中的数据卷。例如创建db1和db2两个容器,并从dadata容器挂在数据卷。

docker run -it --volumes-from dbdata --name db1 ubuntu
docker run -it --volumes-from dbdata --name db2 ubuntu

完成后,db1和db2都挂载同一个数据卷到相同的/dbdata目录。三个容器任何一方在该目录下的写入,其他容器都可以看得到。 可以多次使用--volumes-from从而从多个容器中获得多个数据卷,也可以从其他已经挂在容器卷的容器挂在数据卷。 使用--volumes-from参数所挂载的容器自身不需要保存在运行状态。

删除数据卷容器

如果删除了挂载的容器比如删除了dbdata,db1,数据卷并不会被自动删除,因为此时还有一个db2正在使用这个数据卷容器。 如果想删除一个数据卷必须在删除最后一个还挂载着它的容器时显式使用docker rm-v命令来制定同时删除关联的容器。

利用数据卷容器迁移数据

可以利用数据卷容器对其中的数据卷进行备份、回复,以实现数据的迁移。

备份

使用下面的命令来备份dbdata数据卷容器内的数据卷:

docker run --volumes-from dbdata -v $(pwd):/backup --name worker ubuntu tar cvf /backup/backup.tar /dbdata

很长,慢慢分析,首先是要建立一个利用ubuntu镜像容器,把本地当前路径挂在到容器的/backup下,容器执行的命令是tar cvf /backup/backup.tar /dbdata,命令达到的效果就是吧/dbdata压缩并保存到/backup下。 因为/backup是由本地当前路径挂载的,因此就是保存到当前路径下,从而实现备份的效果。

恢复

如果要恢复数据到容器中,可以按照下面的操作。 首先创建一个带有数据卷的容器dbdata2:

docker run -v /dbdata --name dbdata2 ubuntu /bin/bash

然后创建另一个新容器,挂在dbdata2容器,使用tar解压备份文件到所挂载的容器卷中即可:

docker run --volumes-from dbdata2 -v $(pwd):/backup ubuntu tar xvf /backup/backup.tar

容器是廉价的,数据是宝贵的,根据数据卷和本地的挂载以及容器之间容器卷的共享实现数据备份和还原很方便(说的好违心),总之,数据备份和恢复是没有问题了。

2015/06/05 10:25 am posted in  Docker

搭建和使用Docker私有仓库

有没有感觉下载DockerHub的镜像很慢?至少我感觉慢的不行了,恰好学到了怎么搭建私有仓库,可以把常用的镜像在本地管理。 安装Docker之后,可以是使用官方提供的registry镜像来搭建一套本地私有仓库环境:

docker run -d -p 5000:5000 registry

输入之后就可以等待了,其实本地仓库本身就是容器,这句命令会下载并创建一个registry容器,创建本地的私有仓库。 默认情况下,会将仓库创建在容器的tmp/registry目录下,当然,可以通过-v参数将镜像文件存放到本地的指定路径上。 因为我的腾讯云加了一个10G的数据卷mount在了/data,所以直接:

docker run -d -p 5000:5000 --name registry -v /data/registry:/tmp/registry registry

然后就建好了本地的仓库。比如先上传一个ubuntu的镜像。 然后就可以管理这个私有仓库,上传镜像前需要使用docker tag命令将这个镜像标记为127.0.0.1:500/ubuntu。 标签的格式为:[:TAG][REGISTRYHOST/][USERNAME/]NAME[:TAG]

docker tag ubuntu:latest 127.0.0.1:5000/ubuntu
docker images 
REPOSITORY                    TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
dbserver                      lastest             b81857c55926        19 hours ago        281.8 MB
ubuntu                        14.04               ab80404d13d5        25 hours ago        215.4 MB
mysql                         latest              e0db8fe06e30        36 hours ago        283.5 MB
ubuntu                        latest              fa81ed084842        4 days ago          188.3 MB
127.0.0.1:5000/ubuntu         latest              fa81ed084842        4 days ago          188.3 MB
registry                      latest              d849e35be7b0        9 days ago          413.9 MB

可以使用docker pull上传标记的镜像:

docker push 127.0.0.1:5000/ubuntu

在获取镜像的时候,只需要在前面加127.0.0.1:5000便可以了。 不过这只是在本地提供服务,因为Docker传输要求https,因此还没有尝试证书问题,所以对外提供服务等有机会再尝试。

2015/06/05 10:24 am posted in  Docker