Fari

Docker

重启网卡后docker容器的web服务不可用

我使用docker部署了几个web服务,这些web服务会暴露出一些端口出来提供服务。

现象

如果我使用下面的命令重启了计算机网络

nmcli networking off && nmcli networking on

则docker提供的那些web服务就不可用了

# 在宿主机中执行以下命令,8096是docker容器中web服务暴露出的端口
curl localhost:8096
> curl (56) Recv failure: Connection reset by peer

但是,如果进入到容器内部发现服务确实是可用的

docker exec -it CONTAINER bash
# 下面命令是在容器内执行的
curl localhost:8096
> <html>...</html>

通过 docker ps 命令查看容器确实正常运行的,也确实有把端口暴露出来

排查

从上面的现象可以看出,服务运行是没问题的,那么问题肯定出在容器到宿主机直接的网络转换过程了,docker中容器同宿主机通信是建立在docker建立的虚拟网卡之上的

使用 netstat -rn 或者 ip r 查看宿主机路由表时会发现没有docker创建的虚拟网卡,正常情况下docker会在启动时创建名为docker0的虚拟网卡,并且也会为每个桥接网络的容器创建一个单独的虚拟网卡:

这就说明应该是重启网络时,这些虚拟网卡都被清除了

解决

简单方式就是重启网络后再重启docker,它就会重新创建虚拟网卡

systemctl restart docker

如果容器创建时没有指定 restart: unless-stopped 参数,则还需要手动将容器启动

docker save和docker export及docer commit命令的区别

注:使用import迁移容器后,直接暗转原始run命令是运行不了的,还需要在run命令后面接上容器启动后的命令参数,在原宿主机使用 docker ps –no-trunc 可查看

参考来源:http://www.doczj.com/doc/d95212547.html

即:commit用来将容器dump为镜像,然后使用save将该镜像持久化为文件进行存储和发送

export则类似以上两步的合并,直接将容器持久化为文件

区别在于,前者是新加了一个layer,并保存完整的镜像层,而后者仅仅相当于当前容器的快照,相当于将所有的镜像层合并为了一层

用法:

docker commit -a "提交的镜像作者" -m "提交时的说明文字" a404  ubuntu:load

docker save -o ubuntu.tar ubuntu:load
docker load -i ubuntu.tar

docker export -o ubuntu.tar a404
docker import ubuntu.tar ubuntu:import

traefik教程

使用traefik创建反向代理过程见:https://blog.woyou.cool/post/1908

本文来自官网文档的部分翻译并加以解释:https://doc.traefik.io/traefik/

由于本人不善描述,故请结合下述举例进行了解

简述

traefik是一个边际路由,它在服务中扮演着网关的角色。它拦截所有请求并进行一系列处理后再传递到具体的处理服务器。

其特点是可以进行自动的服务发现。例如,我希望在项目中添加一个新的服务,如果使用nginx的话就需要手动修改nginx配置文件,并创建路由规则。而traefik则会自动监听服务状态,新加的服务也会根据规则自动创建路由。这就使得它在初始配置时略微麻烦,但是后续使用的过程比较简单。

重要概念

traefik有几个较为重要的概念:

配置文件

traefik有两种非常重要的配置文件:静态配置和动态配置

静态配置就是一个 traefik.yml 的配置文件,这里面配置的是traefik的一些启动参数,例如其监听的端口、是否开始dashboard等

动态配置就是用来配置各种路由规则的。traefik一旦启动后就不需要再进行配置了,所有新加入的服务都应该在该服务启动时就指定路由规则,然后等待traefik自动发现。

例如:我启动了一个traefik,此时需要新加入一个web服务S,则我应该在启动S时给定一个启动参数“rule=my-router-rule”用于路由的配置(真实的配置不是这样,但也是kv的形式),这个参数就是一个动态配置。由于不同的provider中服务的启动方式不同,故动态配置都是不尽相同的。例如若使用docker作为provider,则启动新的docker容器时,就需要加上 --label rule=my-router-rule 的参数。因为docker做provider时是使用容器的label作为动态配置的。

(三个月后的后记:不得不说,这种配置方式,当时一时爽,后来火葬场。当我再次进入这个服务器看的时候,我发现我完全忘记了当时是怎么配置的,我完全不知道访问规则是什么,就算我找到了域名解析规则,但我依旧无法想起它到底是怎么访问的,为什么可以这样,我已经怀疑人生到不会组织语言了)

举例

开启一个traefik并使用docker作为provider

#traefik静态配置文件 traefik.yml

# 使用docker作为provider
providers:
  docker: {}

# 使用安全模式并开启dashboard
api:
  dashboard: true
  insecure:  false

启动traefik

docker run -d -p 8080:8080 -p 80:80 -p 443:443 --name traefik -v /var/run/docker.sock:/var/run/docker.sock -v /home/hunt/Templates/traefik.yml:/etc/traefik/traefik.yml --network net1 -l "traefik.http.routers.dashboard.rule=Host(\`traefik.huntzou.com\`) && (PathPrefix(\`/api\`) || PathPrefix(\`/dashboard\`))" -l "traefik.http.routers.dashboard.service=api@internal" traefik

为了便于使用域名访问,我手动在host文件中添加了一些dns解析,其中192.168.190.128就是我的服务器ip

docker中使用traefik作反向代理

拉取traefik镜像

docker镜像地址:https://hub.docker.com/_/traefik

docker pull traefik

创建traefik配置文件

traefik.yml

# Docker configuration backend
providers:
  docker:
    defaultRule: "Host(`{{ trimPrefix `/` .Name }}.your-domain`)"

# API and dashboard configuration
api:
  insecure: false
  dashboard: true

注:配置文件中的 your-domain 应替换成服务器的域名,前面那一串就是将被代理服务器名拼接成二级域名,当使用该二级域名访问时就会重定向到对应的服务器

启动traefik

docker run -d -p 8080:8080 -p 80:80 --name traefik -v /var/run/docker.sock:/var/run/docker.sock -v /home/hunt/Templates/traefik.yml:/etc/traefik/traefik.yml --network net1 -l "traefik.http.routers.dashboard.rule=Host(\`traefik.huntzou.com\`) && (PathPrefix(\`/api\`) || PathPrefix(\`/dashboard\`))" -l "traefik.http.routers.dashboard.service=api@internal" traefik

注:

  1. 也可以不挂载traefik.yml文件,在运行traefik之后使用docker cp将其复制到挂载的位置

  2. 挂载 docker.sock 目的是监听docker容器的变化,当有新的容器被创建时traefik就能自动监测到并创建代理规则。sock=socket

  3. 需将traefik和被代理服务器放在同一个network下

  4. 8080端口用于traefik的控制面板,80和443将用作web服务端口

  5. 启动命令中的两个 -l 参数是为了在安全模式下启动dashboard

  6. 由于linux命令行中的反引号有特殊意义,故这里都需要转义

  7. 关于安全模式下的dashboard配置较为复杂,详见:https://doc.traefik.io/traefik/operations/dashboard/

启动web服务

我这里使用两个nginx当作被代理服务器

sudo docker run -d --name nginx1 --network net1 nginx
sudo docker run -d --name nginx2 --network net1 nginx

打开traefik控制台页面就能看到其自动检测出了nginx,并根据其配置文件生成了二级域名映射。

Docker常用工具

docker engine

平常说的docker一般就是值docker engine,是由cli及守护程序等组成的一整个docker应用程序,简单类比为电脑上装的QQ、浏览器等软件 file

docker machine

用于管理多台安装有docker engine的主机,比如我有十台电脑,我就可以通过docker machine同时配置和管理这十台电脑上的docker。或者也可以用它创建虚拟机(在虚拟机软件上创建,例如VirtualBox)然后进行管理 file

docker compose

用于组织和编排单台主机上的docker容器,比如我希望创建一个服务,该服务包含一个nginx的docker容器,一个service的docker容器和一个mysql的docker容器。

docker swarm

将多台装有docker的主机组合成一个docker集群,并将该集群抽象成单个docker主机,所有对该抽象主机的操作都将同步到集群中各个主机上,且会对该集群的主机进行集群管理,例如容灾处理等 并且通过创建overlay网络使得集群中各主机间能正常通信

与docker machine的区别

可以简单理解为docker machine是对主机上的docker engine进行管理,docker swarm则是对docker中运行的服务进行管理。类比于一个网吧,网吧老板负责机器的采购升级等,而网管则负责将这些电脑连成一个网络并维护该网络

组合使用案例

  1. 使用docker-machine给多台机器安装和配置docker engine
  2. 使用docker-swarm将这些机器汇聚在一起,形成一个虚拟的单一的docker节点
  3. 使用docker-compose编排好你项目所需要的服务,并通过docker-swarm部署到虚拟节点上即可

什么是容器

之前一直把容器理解成虚拟机,容器内的用户是独立的,与宿主机无关的,容器内的进程也是完全和宿主机隔离的。今天才知道这基本上是错误的,这是被vmware这类虚拟机形成思维定势了。

使用vmware创建一个虚拟机就好像在现实世界中,使用计算机完全模拟出来一个虚拟世界,这个虚拟世界的环境和真实世界一模一样,只不过它是用计算机模拟的。这个虚拟世界中出现任何问题都不会对现实世界造成影响,虚拟世界的人口膨胀到人挤人了,现实世界该咋样还咋样。

但是容器不同,容器使用的是命名空间的技术,就是贴标签,所有的人都是真实存在的,他们共享同一片土地。但是给他们穿上了不同的衣服,红色衣服的人只能看到穿红色衣服的人,绿色的只能看到绿色衣服的人,总之就是只能看到同标签的人。那么,如果哪天穿红色衣服的人发生了人口膨胀,那势必穿绿色衣服人的生存空间也会受到影响——即使他们互相看不到对方。

这就意味着,你在容器中运行一个MySQL进程,实际上在宿主机是真的有一个MySQL进程在运行的

可以这样理解,当你创建一个容器后,它会先创建一堆文件,例如/usr、/opt等等,这些文件也是真的在宿主机存在的,只不过它们会分配一个命名空间(标签),例如实际创建的/usr目录可能是 xxx:/usr,而所有带有xxx的文件或者进程,也都只能看到带有xxx的文件或进程。

但是从宿主机的角度来看,不管带有什么标签,一个进程就是一个进程,文件就是文件。这就是为什么明明是在虚拟机中运行的MySQL,却能在宿主机上通过top看到(可能pid显示不同)。也就是说,如果你在容器中启动了两个进程,你在宿主机中kill掉其中一个,那另一个进程仍然是在容器中正常运行的。

另一个非常重要的概念就是用户映射了,既然容器中的进程和文件都是实实在在在宿主机上的,那么如果我在容器中创建了一个文件或者启动一个进程,那这个文件或进程是属于哪个用户的呢?

实际上容器中的用户和宿主机的某个用户本质上是同一个用户,它们之间通过用户映射关系进行绑定。例如在宿主机下我有一个用户hunt,当我用该用户创建了一个容器时,容器运行时(docker、containerd等)会在容器中创建创建一个用户(比如root),并将root映射到宿主机上的hunt,它们其实本质上就是同一个用户在不同的命名空间下的映射,不同命名空间下拥有的权限也不同。所以此时我在容器中以root身份启动了一个MySQL服务,而在宿主机中看到的其实是hunt启动的一个MySQL服务。那如果我手动在容器中创建一个用户,然后用这个用户启动一个进程,宿主机看到的是什么呢?这就得看容器运行时的具体实现了,可能使用启动容器运行时的账户,可能使用一个匿名账户等等。