Docker 容器化多应用部署

对于云上部署一直没有找到一个比较简单舒服的方式,花了点时间终于把 Docker 常用的一个基本套路跑通了,本文结合 Docker + Nginx + Letsencrypt 在同一台 Linux 服务器上部署多个应用,让我们这些低流量单鸡也能轻松部署多个站点,并且让其支持 HTTPS 访问,文中以 Ruby/Sinatra 应用为例,当然不局限于此,你也可以用同样的方法来部署 Python、PHP 或者静态站点。

Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化;

初识 Docker

这一节简单介绍了我个人所理解使用 Docker 的最基本的概念,不作深入展开讨论,更多的可以查看 Docker 中文文档,如果你已经使用过 Docker 可以跳过本节。

镜像 Image

镜像就像以前我们安装 Windows 系统所用的系统光盘,通过 “还原” 可以复制一份系统出来;这就和 Docker 的镜像概念有些类似,只是这个镜像更自动化、高度扩展、内容更丰富,我们的 Web 应用就打包在这个里面。

Windows 年代不是很流行 GHOST 吗,一键 GHOST 选择一个后缀名为 .gho 的文件还原系统,那时候我们只需要制作好系统镜像,然后把镜像文件刻录成光盘就是一个系统盘了,给别人来个 “一键还原” 就克隆出一台一摸一样的操作系统。

容器 Container

同上的概念,容器可以看作通过一个系统光盘还原出的系统,这个系统是我们平时使用的系统,当然你还可以抹掉重装,而我们开机关机(Run/Stop)都是针对于系统(容器),容器是从一个镜像生成出来的。

Dockerfile

我们通过一个 Dockerfile 来描述一个镜像(应用),这个应用可以从本地进行编译,也可以从远端进行拉取(事先编译好后上传到到服务器),比如你可以执行 docker run hello-world 来从远端拉取一个已经编译好了的镜像到本地并运行。

下面以一个 Ruby App 的 Dockerfile 为例来构造一个镜像:

FROM ruby:2.5.1-alpine         // 指定 Ruby 版本和 linux 系统

WORKDIR /app                   // 容器内的工作目录
ADD . /app                     // 拷贝当前目录到容器中

RUN bundle install --system    // 在容器内拉取 ruby app 所需依赖

EXPOSE 80                      // 将容器内的 80 端口暴露出来

CMD ["ruby", "app.rb", "-p", "80", "-o", "0.0.0.0"]    // ruby app.rb -p 80 -o 0.0.0.0

然后通过 build 命令来将这个 Dockerfile 编译成镜像,并指定镜像名为 myapp:

docker build -t myapp .

编译会执行 Dockerfile 中描述的各种指令,如下载系统-拷贝目录-拉取依赖-执行 SHELL 命令等等;但这时候这个容器并没有跑起来,只是被编译成镜像了而已,可以通过 docker images 查看:

REPOSITORY  TAG     IMAGE ID      CREATED       SIZE
myapp       latest  1dd7b4ab48b0  1 minute ago  106MB

然后执行 docker run 就可以生成一个容器跑起来了。

docker run -d myapp
  • -d: 后台运行容器
  • --name 指定一个容器的名字
  • ...

Docker Compose

容器运行可以有很多参数,比如指定映射宿主主机的端口、环境变量等等,包在一个 docker run 命令后就太麻烦了,所以通常用 docker-compose 来组织运行,它可以运行多个容器,并且可以指定依赖关系,以 YAML 格式存储可读性更高。

version: '3'                            // docker-compose 版本

services:                               // 组织哪些容器
  myapp:                                // 第一个容器
    image: myapp                        // 镜像名
    container_name: myapp               // 容器名
    restart: always                     // 容器重启策略
    environment:                        // 环境变量
      - VIRTUAL_HOST=myapp.com          // nginx-proxy 所用域名
      - LETSENCRYPT_HOST=myapp.com      // letsencrypt-nginx-proxy-companion
      - LETSENCRYPT_EMAIL=myapp@yo.com  // letsencrypt-nginx-proxy-companion
      - REDIS_URL=redis://redis:6379    // Redis 连接URL
    networks:                           // 网络 (用于容器间通信)
      - nginx-proxy
      - redis-net
    depends_on:                         // 依赖 redis 容器提供的服务
      - redis

  redis:
    image: redis
    container_name: redis
    hostname:  redis
    restart: always
    networks:
      - redis-net
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - 'redis-data:/data'

networks:                               // 定义网络
  nginx-proxy:
    external:
      name: nginx-proxy
  redis-net:
    driver: bridge

volumes:
  redis-data:

通过 docker-compose up 命令来执行这一系列容器构建:

docker-compose up -d

网络 Network

容器之间是通过 Docker 的虚拟网络来通信的,在 docker-compose.yml 中 Docker 会自动为每一个容器创建一个默认的网络,我们可以通过指定网络来建立通信,如果要跨 docker-compose 容器通信就需要定义外部网络(external) 来实现了;

我们可以通过 docker network 命令创建一个网络:

docker network create redis-net

Docker Compose 的默认网络模式是 bridge,关于网络模式更多信息可以查看官方文档 Networking in Compose

持久化 Volume

容器的生命周期是没有保障的,挂了的话可能随时可能会通过镜像重新创建,如果重新创建容器,那数据就都没有了,那我们存在容器里的数据就很不安全,所以这时候就有了 Volume 的概念可以让我们把某些目录或文件映射到宿主主机,不受容器生命周期而删除掉,当然,如果是要做数据的安全备份那又是另外一个话题了,这里只是介绍如何把容器内的数据保存到主机上。

Nginx-proxy

重要的话题来了,如何在一台机子上暴露多个容器的服务到外网呢?我们可以使用 nginx-proxy 来自动处理 Docker Nginx 反向代理,实现同一台服务器上运行多个容器应用,分别绑定各自不同的域名进行访问。

Docker 提供 inspect 命令可以在运行时获取一个容器的元信息,比如 IP、端口等,和另一个 events 命令来获取一些容器启动、停止的钩子回调,nginx-proxy 正式通过这些 API 来自动生成 Nginx 反向代理的回调,关于 Docker 自动 Nginx 反响代理可以查看这篇文章《Automated Nginx Reverse Proxy for Docker》

nginx-proxy

我们可以建立一个 nginx-proxy 用来反向代理的容器配置、Nginx 配置等等,目录结构大致如下:

.
└── nginx-proxy
    ├── docker-compose.yml
    └── nginx
        ├── certs            // 存放 SSL 证书
        ├── conf.d
        │   └── default.conf // 后面自动生成

你需要切到然后切到 nginx-proxy 目录执行 Docker 相关操作,下面例子的 volumes 都使用的相对路径。

下面看一个例子 docker-compose.yml :

version: '3'

services:
  nginx-proxy:
    image: jwilder/nginx-proxy
    container_name: nginx-proxy
    ports: 
      - "80:80"
    restart: always
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - ./nginx/nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl:ro

networks:
  default:
    external:
      name: nginx-proxy

运行之前我们需要先下载 nginx-gen 所需的 nginx.tmpl 模版:

curl https://raw.githubusercontent.com/jwilder/nginx-proxy/master/nginx.tmpl > ./nginx/nginx.tmpl

现在我们可以通过 docker-compose up 跑起来 nginx-proxy,并加入到名为 nginx-proxy 的网络中,这时候就会启动自动监听其他容器的启动事件,并自动生成 Nginx 配置:

nginx-proxy    | dockergen.1 | 2018/09/10 11:13:57 Received event die for container a08a5c556994
nginx-proxy    | dockergen.1 | 2018/09/10 11:13:58 Received event stop for container a08a5c556994
nginx-proxy    | dockergen.1 | 2018/09/10 11:13:58 Generated '/etc/nginx/conf.d/default.conf' from 2 containers
nginx-proxy    | dockergen.1 | 2018/09/10 11:13:58 Running 'nginx -s reload'
nginx-proxy    | dockergen.1 | 2018/09/10 11:13:59 Received event start for container a08a5c556994
nginx-proxy    | dockergen.1 | 2018/09/10 11:13:59 Generated '/etc/nginx/conf.d/default.conf' from 3 containers
nginx-proxy    | dockergen.1 | 2018/09/10 11:13:59 Running 'nginx -s reload'
nginx-proxy    | dockergen.1 | 2018/09/10 11:14:00 Contents of /etc/nginx/conf.d/default.conf did not change. Skipping notification 'nginx -s reload' 

现在你再挂载其他容器,并向外暴露 80 端口并通过环境变量 VIRTUAL_HOST 指定绑定域名即可访问了。

Let's Encrypt

如今 HTTPS 已经成为标配,苹果爸爸在 iOS 上甚至要求默认必须启用 HTTPS 网络请求访问,可见其重要性, Let's Encrypt 是一个免费的 SSL 证书颁发机构,也可以通过 certbot 之类的工具来自动生成或更新,不过搭配 nginx-proxy 则可以用一个更为方便的东西叫 JrCs/docker-letsencrypt-nginx-proxy-companion 来创建、续订 Let's Encrypt 证书,有一张图很好的解释了它们的工作原理:

docker-letsencrypt-nginx-proxy-companion

继续上面的例子,修改 docker-compose.yml 如下:

version: '2'

services:
  nginx-proxy:
    image: jwilder/nginx-proxy
    container_name: nginx-proxy
    labels:
        com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy: "true"
    ports: 
      - "80:80"
      - "443:443"
    restart: always
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./nginx/vhost.d:/etc/nginx/vhost.d
      - ./nginx/html/:/usr/share/nginx/html
      - ./certs:/etc/nginx/certs
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - ./nginx/nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl:ro

  letsencrypt-nginx-proxy-companion:
    image: jrcs/letsencrypt-nginx-proxy-companion
    container_name: letsencrypt-nginx-proxy-companion
    volumes_from:
      - nginx-proxy
    volumes:
      - ./certs:/etc/nginx/certs:rw
      - /var/run/docker.sock:/var/run/docker.sock:ro
    restart: always

networks:
  default:
    external:
      name: nginx-proxy

这里我们新增了 443 端口为了启用 HTTPS,并引 letsencrypt-nginx-proxy-companion 用来自动生成 letsencrypt 证书,现在你可以通过 https 访问了,一条龙自动化服务真的感觉不错哦~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,454评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,553评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,921评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,648评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,770评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,950评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,090评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,817评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,275评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,592评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,724评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,409评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,052评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,815评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,043评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,503评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,627评论 2 350

推荐阅读更多精彩内容