惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Hacker News - Newest:
Hacker News - Newest: "LLM"
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
L
LINUX DO - 最新话题
Cloudbric
Cloudbric
N
News and Events Feed by Topic
S
Secure Thoughts
Vercel News
Vercel News
S
Security @ Cisco Blogs
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
S
SegmentFault 最新的问题
Hacker News: Ask HN
Hacker News: Ask HN
博客园 - 聂微东
WordPress大学
WordPress大学
Google Online Security Blog
Google Online Security Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Google DeepMind News
Google DeepMind News
PCI Perspectives
PCI Perspectives
雷峰网
雷峰网
Hugging Face - Blog
Hugging Face - Blog
Blog — PlanetScale
Blog — PlanetScale
Apple Machine Learning Research
Apple Machine Learning Research
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threat Research - Cisco Blogs
博客园 - 司徒正美
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
A
About on SuperTechFans
P
Proofpoint News Feed
大猫的无限游戏
大猫的无限游戏
V
V2EX
I
Intezer
H
Hacker News: Front Page
www.infosecurity-magazine.com
www.infosecurity-magazine.com
L
Lohrmann on Cybersecurity
F
Fortinet All Blogs
Schneier on Security
Schneier on Security
博客园 - 叶小钗
The Cloudflare Blog
月光博客
月光博客
W
WeLiveSecurity
T
Tenable Blog
P
Proofpoint News Feed
aimingoo的专栏
aimingoo的专栏
Help Net Security
Help Net Security
L
LangChain Blog
C
CERT Recently Published Vulnerability Notes
T
The Exploit Database - CXSecurity.com
美团技术团队
B
Blog

isaced

使用 TypeScript 构建的 appstore-connect-sdk 归档 我用 SwiftUI 写了一个 V2ex 客户端 Swift Server-side SDWebImage 为什么无法读取 webpmux 生成的 WebP 动图? [译] 探索 Swift 4 中新的 String API 对一个 Django 站点做 ab 压力测试 fir-mac 开发笔记 iOS10 Safari 引导用户信任企业签名 Swift Web 开发之 Vapor - 模版 Leaf(三) Swift Web 开发之 Vapor - 路由(二) HoloLens 初体验 Swift Web 开发之 Vapor - 入门(一) Jenkins for iOS 搭建日记 iOS HOOK 注入与重签名 Xcode 使用 Configuration 来配置不同的项目环境 图床从SAE迁移到七牛 给 UIProgressView 脱掉那层微弱的渐变 初用 CocoaAsyncSocket 记一次 SQLite 性能优化 有多色 - 开发小记 海岛风情 - 泰国普吉岛六日游 EGOCache 源码解析 日语五十音假名临摹校验算法 [iOS] UITextView 追加文字自动跳到顶部解决方法 Cocoa 新的依赖管理工具:Carthage 新年快乐! 博客主机换到搬瓦工 写了个iOS小游戏:有多色 iOS 后台任务之 Long-running background task 悄悄的就必须用 XCode 5.1.1 上传了! HTTP Content-type 与 AFNetworking Objective-Cloud 初体验(1) 掌中站上架小记 NSLoger 上线了 Python Markdown 做语法限制 Nginx + Gunicorn + Django 部署小记 用Shell脚本批量裁剪App Icon各个尺寸 LaunchRocket使用小记 用宏提速NSCoding 2014新年伊始:近期读过的好文推荐 [iOS] stringWithFormat 和 initWithFormat 有何不同? Apache + Flask + mod_wsgi + Koding 部署小记 收集几个Objective-C的HTML解析库 NSMapTable: 不只是一个能放weak指针的 NSDictionary 2013,再见! Emlog到Typecho的迁移工具:emlog2typecho [iOS]给上拉下拉刷新PullToRefresh做本地化 [iOS]代码推荐:大图缩放视图 - Vertigo iOS:hidesBottomBarWhenPushed的正确用法 Xcode 5新的Interface Builder,你准备好了吗? [翻译]如何使用Django创建简单的博客 我的Mac音乐播放器 初识 NSDataDetector Objective-C 计算代码运行时间 WordPress上传文件自动重命名 iOS:仿jing.fm的音乐播放视图 我的啃黑苹果之路 关于 搜索 项目
Docker 容器化多应用部署
2018-09-10 · via isaced

Featured image of post Docker 容器化多应用部署

以一个 Ruby 应用为例介绍如何基于 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:

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

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

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

  • -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 命令来执行这一系列容器构建:

网络 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 访问了,一条龙自动化服务真的感觉不错哦~