本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 署名 4.0 国际 (CC BY 4.0)

本文作者: 苏洋

创建时间: 2018年08月28日 统计字数: 10813字 阅读时间: 22分钟阅读 本文链接: https://soulteary.com/2018/08/28/better-use-of-docker-and-traefik.html


更完善的 Docker + Traefik 使用方案

在踩坑无数之后,多次修改后,这篇草稿箱中的文字终于得以成型,撒花。

六月更新架构的时候,去掉了 openresty 作为服务器前端,取而代之的是裸跑 Traefik,因为只暴露网关的 80 / 443,后面所有子容器都是以 expose 方案对内暴露端口到一块虚拟网卡上,安全问题也不大,网关挂载着通配符证书,可以方便的添加删除后面的应用,虽说用起来挺舒服的,但是有两点始终让我不是很爽。

  1. 因为 docker 直接使用 iptable 操作端口转发,在不修改 iptable 的前提下,之前积累了大量的 fail2ban 规则无法使用了,也就是说得忍受大量扫描器污染日志。
  2. 因为应用跑完全隔离的方案,子容器能拿得到的客户端 IP 都是虚拟网卡 IP ,想统计个数据,得用对账的方案,比较麻烦。

问了一下之前出去创业的师傅,他们直接在给容器分配公网,壕无人道,而且直接分配公网 IP 配置项目也不少...

如何能在最少配置的情况下,最少资源消耗的情况下,达到现有使用的便捷程度,便更为了我这篇文字的主要目的。

赶在休假结束之前,重新梳理了服务器运行环境和Traefik的使用,记录下来,或许对折腾“免”运维的服务的你也会有帮助。

在折腾具体配置之前,需要先提供标准的系统环境。

基于 Ubuntu 18.04 系统配置 Docker 运行环境

2018年第三个季度,Ubuntu 18.04 更新了快一个季度了,基本上该更新的软件也都更新了,该兼容的软件也都进行了兼容处理;Docker 在经历改名风波后,版本迭代高歌猛进,一大波编排软件蜂拥而至,现在版本也升级到了 v18 。

考虑到后面软件会做越来越多向后兼容的事情,16.04 的维护周期也近半,那么这次就使用最新的系统和软件来进行基础环境的配置吧,这里没有选择 CoreOS 是因为我这里还有一些其他软件的使用需求,想在一个相对中立的环境中使用。

截止这篇文章写成: 我使用的国外主流云厂商皆支持 Ubuntu 18.04, 国内的云厂商中,阿里云支持,但是腾讯云暂时还不支持。

如果你的云主机厂商支持最新版本的系统,那么可以直接参考我下面给出的命令进行基础环境配置。

如果你的云主机厂商不支持,那么请使用 do-release-upgrade 命令先进行手动升级,升级过程中可以一路 yes,以及使用当前软件维护的最新版本: install thepackagemaintainer's version

下面开始介绍如何配置最基础的系统环境。

先更新软件包列表,升级软件版本到最新的稳定版,然后为避免系统中残留一些老古董影响软件运行,我们要进行尝试性的卸载老版本软件操作,以及安装一些常用软件。

  1. apt update && apt upgrade -y
  2. apt remove docker docker-engine docker.io
  3. apt install -y apt-transport-https ca-certificates curl software-properties-common

向系统中添加 Docker 官方 GPG Key,然后验证该 Key 有效性,并更新仓库源到系统,Ubuntu 18.04 会直接触发拉取软件包列表的操作,比较人性化,最后直接敲入 install 命令,进行社区版的 docker 安装即可。

  1. curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
  2. apt-key fingerprint 0EBFCD88
  3. add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
  4. apt install -y docker-ce

在 https://github.com/docker/compose/releases 找到最新稳定版本安装。

  1. curl -L https://github.com/docker/compose/releases/download/1.22.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
  2. chmod +x /usr/local/bin/docker-compose

至此,基础的 docker 环境就安装就绪了。

  1. Docker version 18.06.1-ce, build e68fc7a
  2. docker-compose version 1.22.0, build f46880fe
  3. Storage Driver: overlay2

简单的系统加固

基础的修改 ssh 端口,规避扫描器,应该人人都会,就略过不提,如果不会可以百度或者翻阅之前的博客文章,我们来说说 ufw 防火墙的配置。

服务默认会是未激活状态,在激活之前,务必先豁免 ssh 端口,避免再使用 vnc 登录上去补救。

  1. > ufw status
  2. Status: inactive
  3. ufw all SSH_PORT
  4. Rules updated
  5. Rules updated (v6)

当然,如果你不喜欢修改 SSH 端口的话,可以直接使用下面的命令。

  1. ufw disable
  2. ufw reset
  3. ufw default deny incoming
  4. ufw default allow outgoing
  5. ufw allow 22/tcp
  6. ufw allow 80/tcp
  7. ufw enable

然后激活防火墙状态。

  1. ufw enable
  2. Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
  3. Firewall is active and enabled on system startup

防火墙启动完毕,可以顺便折腾一下 fail2ban ,大幅减少常规的扫描器对于日志的骚扰,和一些初级的猜解、渗透,后面再写一篇详述。

Docker 端口绑定和 UFW 的冲突

在配置完毕防火墙后,接下来可以试验配置是否生效,启动一个映射到 80 端口的 nginx:alpine 镜像,然后浏览器或者命令行访问服务器公网IP,可以看到熟悉的 nginx 默认欢迎页,ufw 并没有什么作用。

  1. root@VM-0-10-ubuntu:~# docker run --rm -p 80:80 nginx:alpine
  2. Unable to find image 'nginx:alpine' locally
  3. alpine: Pulling from library/nginx
  4. 911c6d0c7995: Pull complete
  5. 131e13eca73f: Pull complete
  6. 95376bf29516: Pull complete
  7. 6717402ec973: Pull complete
  8. Digest: sha256:23e4dacbc60479fa7f23b3b8e18aad41bd8445706d0538b25ba1d575a6e2410b
  9. Status: Downloaded newer image for nginx:alpine
  10. 114.xxx.xxx.xxx - - [27/Aug/2018:16:44:17 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36" "-"
  11. 2018/08/27 16:44:18 [error] 6#6: *1 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 114.xxx.xxx.xxx, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "119.28.182.48", referrer: "http://119.28.182.48/"
  12. 114.xxx.xxx.xxx - - [27/Aug/2018:16:44:18 +0000] "GET /favicon.ico HTTP/1.1" 404 571 "http://119.28.182.48/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36" "-"

这里 ufw 没有生效的原因在于 docker 默认使用了 iptable 添加了一些转发规则,压根没有走到 ufw 的规则中。

一般网上推荐的方案是,关闭这个特性,比如使用类似下面的操作。

  1. echo '{"iptables": false}' | sudo tee /etc/docker/daemon.json > /dev/null

但是如果你真的这样做了,接下来你将无法获得客户端访问时,使用的IP信息,各个容器直接访问互联网、以及容器互通方面也会遇到一些小问题,然后你又不得不添加一些 iptable 去修正这个问题。

如果你还是选择关闭 iptable 特性,执行容器,那么如果你想获取客户端IP,便只能使用以下几个方案达成:

  1. 前端启动一个L7的Haproxy / nginx反向代理后面的服务。
  2. 运行模式改为 host ,放弃容器完整虚拟化,将端口直接暴露到 host 上,此时将不再能够通过 docker ps 查看到你的容器端口状态,只能通过 docker network inspect 网卡看到对应的端口开启状态,十分不利于维护。
    • 如果你是使用经典的 docker run 命令,那么需要配合 --net=host 参数。
    • 如果你是使用非 swarm 模式的 compose, 则需要声明 network_mode:"host", compose 版本需要声明 3.2 及以上, port 导出实际并不需要用繁琐的模式定义。
    • 不用尝试创建自定义网络为 host ,截止本文完成时的编排工具版本以及 docker 版本,这个功能不支持。
  3. 修改 ufw 、docker 的 iptable 转发规则,完成你想要的转发方式。

可以看到,不管是哪种方案,搞起来都十分繁琐,而且不利于重复部署,未来调试维护成本太高了。

更好的方案

让 Docker 保持默认配置和行为,但是留出端口控制权给 UFW 以及外层的网关,子容器依旧全部使用 expose 使用私有化的方法导出端口给网关。

这个方案是不是看起来和上面小节中的方案1很相似,但是其实差别还不小,使用 Traefik 可以在不不配 consul / zk的情况下,自动监听 docker daemon 的状况,做到服务发现、负载均衡、可用性自动切换、甚至自动绑定域名证书。

之前不得不说是过分追求全容器方案,导致我使用 Traefik 都是在容器中。虽说升级相对轻松,只需要修改 compose 配置中的版本字段即可,程序可用性也不需要太过关注,直接交付给 Linux Daemon 去维护,但是这样就面临一个问题,网关拿到数据的时候,已经经过了至少两块虚拟网卡的转发,一来浪费性能,二来丢失客户端IP,三来如果要保障更高级别的安全,还得关闭 docker iptable 转发的特性,这个面临的问题,上面的小节里说的够多了。

在决定“裸”运行 Traefik 后,我们需要对它的配置进行一定的改动,我这里提供一份最简单的配置,相信已经可以满足许多常见场景。

  1. ################################################################
  2. # 全局设置
  3. ################################################################
  4. # 激活调试模式 (默认关闭)
  5. debug = true
  6. # 日志等级 (默认 ERROR)
  7. logLevel = "INFO"
  8. # 全局入口点类型 (默认 http)
  9. defaultEntryPoints = ["https", "http"]
  10. # 不上报统计信息
  11. sendAnonymousUsage = false
  12. ################################################################
  13. # 入口点设置
  14. ################################################################
  15. [entryPoints]
  16. # 默认前端
  17. [entryPoints.http]
  18. address = ":80"
  19. compress = true
  20. [entryPoints.http.redirect]
  21. entryPoint = "https"
  22. [entryPoints.https]
  23. address = ":443"
  24. compress = true
  25. [entryPoints.https.tls]
  26. [[entryPoints.https.tls.certificates]]
  27. certFile = "/data/ssl/your.com.cer"
  28. keyFile = "/data/ssl/your.com.key"
  29. # 控制台端口
  30. [entryPoints.traefik-api]
  31. address = ":4399"
  32. # Ping端口
  33. [entryPoints.traefik-ping]
  34. address = ":4398"
  35. ################################################################
  36. # Traefik File configuration
  37. ################################################################
  38. [file]
  39. [backends]
  40. [backends.dashboard]
  41. [backends.dashboard.servers.server1]
  42. url = "http://127.0.0.1:4399"
  43. [backends.ping]
  44. [backends.ping.servers.server1]
  45. url = "http://127.0.0.1:4398"
  46. [frontends]
  47. [frontends.dashboard]
  48. entrypoints = ["https"]
  49. backend = "dashboard"
  50. [frontends.dashboard.routes.route01]
  51. rule = "Host:dashboard.your.com"
  52. [frontends.ping]
  53. entrypoints = ["https"]
  54. backend = "ping"
  55. [frontends.ping.routes.route01]
  56. rule = "Host:ping.your.com"
  57. [frontends.ping.routes.route02]
  58. rule = "ReplacePathRegex: ^/ /ping"
  59. ################################################################
  60. # Traefik logs configuration
  61. ################################################################
  62. # Traefik logs
  63. # Enabled by default and log to stdout
  64. #
  65. # Optional
  66. #
  67. # Default: os.Stdout
  68. [traefikLog]
  69. filePath = "/data/logs/traefik.log"
  70. [accessLog]
  71. filePath = "/data/logs/access.log"
  72. # Format is either "json" or "common".
  73. #
  74. # Optional
  75. # Default: "common"
  76. #
  77. # format = "common"
  78. ################################################################
  79. # 访问日志 配置
  80. ################################################################
  81. # Enable access logs
  82. # By default it will write to stdout and produce logs in the textual
  83. # Common Log Format (CLF), extended with additional fields.
  84. #
  85. # Optional
  86. #
  87. # [accessLog]
  88. # Sets the file path for the access log. If not specified, stdout will be used.
  89. # Intermediate directories are created if necessary.
  90. #
  91. # Optional
  92. # Default: os.Stdout
  93. #
  94. # filePath = "/path/to/log/log.txt"
  95. # Format is either "json" or "common".
  96. #
  97. # Optional
  98. # Default: "common"
  99. #
  100. # format = "common"
  101. ################################################################
  102. # API 及 控制台 配置
  103. ################################################################
  104. # 启用API以及控制台
  105. [api]
  106. # 入口点名称
  107. entryPoint = "traefik-api"
  108. # 开启控制台(默认开启)
  109. dashboard = true
  110. # 默认协议
  111. defaultEntryPoints = ["https"]
  112. ################################################################
  113. # Ping 配置
  114. ################################################################
  115. # 启用 ping
  116. [ping]
  117. # 入口点名称
  118. entryPoint = "traefik-ping"
  119. ################################################################
  120. # Docker 后端配置
  121. ################################################################
  122. # 启用Docker后端
  123. [docker]
  124. # Docker服务后端
  125. endpoint = "unix:///var/run/docker.sock"
  126. # 默认域名
  127. domain = "traefix.your.com"
  128. # 监控docker变化
  129. watch = true
  130. # 使用自定义模板(可选)
  131. # filename = "docker.tmpl"
  132. # 对容器默认进行暴露(默认开启)
  133. # 如果关闭选项,则容器不包含 `traefik.enable=true` 标签,就不会被暴露
  134. exposedbydefault = false
  135. # 使用绑定端口的IP地址取代内部私有网络(默认关闭)
  136. usebindportip = false
  137. # 使用 Swarm Mode (默认关闭)
  138. swarmmode = false
  139. # Enable docker TLS connection.
  140. #
  141. # Optional
  142. #
  143. # [docker.tls]
  144. # ca = "/etc/ssl/ca.crt"
  145. # cert = "/etc/ssl/docker.crt"
  146. # key = "/etc/ssl/docker.key"
  147. # insecureskipverify = true

将配置做适当修改,保存之后,运行即可:

  1. traefik -c /etc/traefik.toml

当然,此时你是无法访问到你的 Traefik 网关提供的服务的,为什么呢,因为这个软件端口绑定会受限制于 UFW 的规则,所以我们要更新 UFW 规则,允许外网访问我们的 80 和 443 端口。

  1. root@VM-0-10-ubuntu:~# ufw allow 80
  2. Rule added
  3. Rule added (v6)

如果你操作顺利,此刻你已经能够顺利访问你的网站了。

当然,这里少了 docker daemon 的协助,进程管理还是要看护一下的,推荐使用 supervisor 进行辅助管理,之前的博客有介绍过不止一次,有兴趣可以翻阅,这里同样给出一份最基础的配置参考:

  1. [program:traefik]
  2. command=traefik -c /etc/traefik.toml --sendAnonymousUsage=false
  3. user=root
  4. autostart=true
  5. startsecs=3
  6. startretries=100
  7. autorestart=true
  8. stderr_logfile=/data/traefik/error.log
  9. stderr_logfile_maxbytes=50MB
  10. stderr_logfile_backups=10
  11. stdout_logfile=/dara/traefik/access.log
  12. stdout_logfile_maxbytes=50MB
  13. stdout_logfile_backups=10

你的网关就就绪之后,我们随便找一个目录使用一个叫做 whoami 的软件镜像帮助我们验证:网关能够如期的使用,除了自动服务发现,负载解析,还能提供包括统计、转发、header重写等功能。

  1. version: '3'
  2. services:
  3. whoami:
  4. image: emilevauge/whoami
  5. expose:
  6. - 80
  7. labels:
  8. - "traefik.enable=true"
  9. - "traefik.port=80"
  10. - "traefik.frontend.rule=Host:who.your.com"

将上面的配置保存为 docker-compose.yml,然后后台运行起来。

浏览器或者命令行访问 who.your.com,获得下面的信息:

  1. Hostname: b0cd60b18550
  2. IP: 127.0.0.1
  3. IP: 172.20.0.2
  4. GET / HTTP/1.1
  5. Host: who.your.com
  6. User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36
  7. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
  8. Accept-Encoding: gzip, deflate, br
  9. Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7
  10. Cache-Control: max-age=0
  11. Upgrade-Insecure-Requests: 1
  12. X-Forwarded-For: 221.xxx.xx.xxx
  13. X-Forwarded-Host: who.your.com
  14. X-Forwarded-Port: 443
  15. X-Forwarded-Proto: https
  16. X-Forwarded-Server: VM-0-10-ubuntu
  17. X-Real-Ip: 221.xxx.xx.xxx

可以看到服务发现、SSL证书挂载、源站IP转发等功能都能够正确使用,而且不出意料,QPS 也会高不少(毕竟少了至少一层网络转发、至少一层完整的虚拟化)。

另外,由于没有修改 docker 的配置,容器不会出现不允许访问外网的情况,简单验证:

  1. root@VM-0-10-ubuntu:/data/who# docker run -it --rm alpine ping -c 1 8.8.8.8
  2. Unable to find image 'alpine:latest' locally
  3. latest: Pulling from library/alpine
  4. 8e3ba11ec2a2: Pull complete
  5. Digest: sha256:7043076348bf5040220df6ad703798fd8593a0918d06d3ce30c6c93be117e430
  6. Status: Downloaded newer image for alpine:latest
  7. PING 8.8.8.8 (8.8.8.8): 56 data bytes
  8. 64 bytes from 8.8.8.8: seq=0 ttl=46 time=14.075 ms
  9. --- 8.8.8.8 ping statistics ---
  10. 1 packets transmitted, 1 packets received, 0% packet loss
  11. round-trip min/avg/max = 14.075/14.075/14.075 ms

接下来就是逐步升级每台服务器以及做数据迁移了。

资源链接

其实关于容器内获取外部IP,社区有大量讨论,比如:[15086](https://github.com/moby/moby/issues/15086#issuecomment-125662376) 等等,涉及不同的网卡模式,不同的编排工具,不同的端口映射模式,又有许多延伸话题。

而 Docker 和 UFW 防火墙的恩怨情仇,其实也是老化长谈,但是不知道为何,网上能看到的资料一边倒到修改 iptable ...

  • Uncomplicated Firewall (UFW) is not blocking anything when using Docker
  • Running Docker behind the ufw firewall

其他

希望本文能够给你一些额外的启示,帮到正在使用 Traefik 和 Docker 来做服务化的你。

更完善的 Docker + Traefik 使用方案相关推荐

  1. 优化Docker镜像大小方案 2017年12月10日 17:54:46 阅读数:1774 标

    2019独角兽企业重金招聘Python工程师标准>>> 优化Docker镜像大小方案 2017年12月10日 17:54:46 阅读数:1774 标签: Docker 更多 个人分类 ...

  2. 流言终结者- Flutter和RN谁才是更好的跨端开发方案?

    背景 论坛上很多小伙伴关心为什么闲鱼选择了Flutter而不选择其他跨端方案?站在质量的角度,高性能是一个很重的因素,我们使用Flutter重写了宝贝详情页之后,对比了Flutter和Native详情 ...

  3. Docker垃圾清理方案

    Docker垃圾清理方案 我们在使用docker的过程中发现基于swarm使用Storage Driver: overlay的方式进行存储.但是发现这个特别占用存储空间. 清理所有停止的容器 dock ...

  4. Docker 容器监控方案随手记

    天气:雨转阴 Docker环境准备 官方安装文档 # 卸载旧 软件 yum remove docker \docker-client \docker-client-latest \docker-com ...

  5. 如何利用工时表来帮助项目管理做得更完善?

    项目管理是一项复杂的任务,需要协调各种资源以确保项目按时交付.其中一个关键方面是管理各个员工工时.工时表软件是一种可以帮助企业记录各个员工工作时效的工具,而且还可以帮助项目管理者记录和跟踪项目成员的时 ...

  6. 计算机专业相关的创意产品,十款电脑支架设计欣赏,给你更聪明的十个产品设计创意方案...

    十款电脑支架设计欣赏,给你更聪明的十个产品设计创意方案 支架是数码产品配件中的重要类别,支架能够支撑数码产品,解放你的双手,让你影音娱乐更加轻松自如,电脑支架不外如是,那么有什么样的电脑支架设计创意让 ...

  7. 计算机相关的创新设计方案,十款电脑支架设计欣赏,给你更聪明的十个产品设计创意方案...

    十款电脑支架设计欣赏,给你更聪明的十个产品设计创意方案 支架是数码产品配件中的重要类别,支架能够支撑数码产品,解放你的双手,让你影音娱乐更加轻松自如,电脑支架不外如是,那么有什么样的电脑支架设计创意让 ...

  8. 微博更经济的异地容灾方案是怎么搞的

    写在前面 中国的互联网独角兽的体量都是非常大的,由于中国人口众多,任何一家互联网企业想在中国的互联网圈子立足,都需要生长到一个非常大的规模,也就是说这家独角兽企业承载的数据与服务的量都相当巨大. 在如 ...

  9. 介绍一种更方便的代理池实现方案

    现在搞爬虫,代理是不可或缺的资源. 代理池 为了保证代理的有效性,我们往往可能需要维护一个代理池.这个代理池里面存着非常多的代理,同时代理池还会定时爬取代理来补充到代理池中,同时还会不断检测其中代理的 ...

最新文章

  1. SQL 基础之DDL语句创建和管理表(十四)
  2. 【Ex_BSGSBSGS算法模板】poj2417 poj3243
  3. Extjs EditorGridPanel中ComboBox列的显示问题
  4. redis解决“高并发定时秒杀”库存误差问题
  5. 第三次学JAVA再学不好就吃翔(part92)--Map集合的遍历
  6. [PAT乙级]1006 换个格式输出整数
  7. 怎么判断linux22端口是否通,在Linux环境下使用SSH判断端口是否通(示例代码)
  8. 数据分析系统DIY1/3:CentOS7+MariaDB安装纪实
  9. jsonrpc php使用,php实现的一个简单jsonrpc框架实例
  10. 电脑硬盘坏道怎么检测,又如何修复图文教程
  11. Unity(十九):获取编辑器内置样式和内置图标
  12. 【高等数学】弧微分、渐近线、曲率和曲率半径
  13. Python使用openpyxl插入excel批注,修改批注
  14. 计算机专业职称入深户,深圳市人才引进入深户新政策
  15. iOS开发IPhone以及iPad尺寸汇总
  16. 情绪文本的时空可视化分析
  17. Linux 多点电容触摸屏实验(1)
  18. HGDB 数据库用户密码参数查询设置
  19. hash和btree索引的区别
  20. C/C++中substr函数的应用(简单讲解)

热门文章

  1. 小米linux平板触摸屏驱动安装方法,windows10系统怎样安装触摸屏的驱动
  2. Navicat premium 导入Excel文件失败
  3. c4d里.gil和.gi2是什么文件?怎么打开
  4. 微信居然有“隐身功能”了?
  5. [转]WIN7服务一些优化方法
  6. Mysql更新百万历史数据
  7. 怎么把vue改写成html,vue将字符串转为为html
  8. karabiner macOs Sierra不可用后的临时解决方法
  9. 客户数据平台(CDP)是什么?
  10. Flask源码阅读(六)——Flash消息