docker学习笔记
docker
相关概念
Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源。Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app),更重要的是容器性能开销极低。——菜鸟教程
docker三要素
镜像
类似面向对象编程语言中类的概念,在面向对象编程语言中,通过一个模板构造一个实例,这个‘模板’类似于docker中镜像的概念。类比使用过的虚拟化软件 vmware
,镜像类似其中的iso映像文件。
容器
容器是可写的,相当于一个运行的实例。通过基础镜像进行创建。在面向对象编程语言中,通常我们需要这样去创建一个实例,这里拿java举例
1 | Student stu1 = new Student() |
而在docker中我们是这样创建一个容器的。
1 | docker run [镜像名称/ID] |
镜像仓库
镜像仓库顾名思义就是存放镜像的地方,类似代码仓库。方便镜像的管理。
相关的操作和git的那一套类似。
基础命令
描述 | cmd | 常用参数\说明 |
---|---|---|
启动一个容器 | docker run 镜像名称 | -d 后台运行 -it 以交互式终端运行容器 –name给容器分配别名,不分配docker会默认分配 -p指定端口映射 -P随机端口映射 |
查看当前正在运行的容器 | docker ps | -a 列出所有的容器,包括已经停止的 |
查看本地存储的所有镜像 | docker images | |
在运行的容器内执行命令 | docker exec 容器名称或者ID 命令 | 常见的例如需要进行交互的容器:操作系统镜像 |
停止容器 | docker stop 容器名称或ID | 用于停止某个运行中的容器,可以停止多个 |
重新启动一个容器 | docker restart 容器名称或ID | 用于重启正在运行或者已经停止运行的容器 |
启动停止的容器 | docker start 容器名称或ID | 用于启动已经停止的容器 |
删除已经停止的容器 | docker rm 容器名称或ID | 用于删除已经停止的容器,如果该容器不是停止状态,执行该命令会报错,可以加上-f参数进行强制删除 |
删除镜像 | docker rmi 镜像名称或ID | 用于删除镜像 |
查找镜像 | docker search 镜像名称 | 通常在docker pull之前需要查找以下远程的镜像仓库。 |
拉取镜像 | docker pull 镜像名称 | 类似git pull从远程仓库拉取一个镜像 |
推送镜像 | docker push 构件好的镜像 | 类似git push,将本地的镜像推送到远程仓库 |
查看容器运行日志 | docker logs 容器名称或ID | |
查看容器或者镜像的详细信息 | docker inspect 容器或镜像名称/ID |
mysql容器实战
如果不小心删除了容器,没有对数据进行一个备份的话,将造成严重的问题。docker提供了数据卷这个东西。即使删除了容器实
例,下次再创建新的容器实例,按照备份的数据卷依然能够恢复到删除时的状态。并且配置能够复用!
1 | docker run |
图形化管理界面
通常我们不会直接在容器里面写相关的sql语句去操纵数据库,而是通过一些图形化界面自动生成sql脚本去操纵数据库,下面要用到一个数据库连接管理工具。同样是容器化过后的。
phpmyadmin
- 拉取镜像
1 | docker pull phpmyadmin |
- 创建docker网络
mysql容器要与phpmyadmin容器产生“交互”,当然少不了建立连接,在docker当中建立一个可以沟通的网络。
1 | docker network create net1 |
查看网络是否已经创建
1 | docker network ls |
这里的net1是自己定义的一个网络别名。
- 启动phpmyadmin容器
1 | docker run \ |
所有东西都准备好了之后,访问http://localhost:8080 就可以看到phpadmin的登录页面
并且可以看到,之前备份的数据还是存在的,这里是我之前备份的一些数据卷
至此,环境的搭建告一段落了……
踩坑点
注意文件的权限问题
1 | ls -l |
有时候配置加载与数据恢复错误很大概率是文件权限的问题。不想被权限问题困扰太多建议切换到root用户。
首先要先去排查日志。通过
1 | docker logs 容器名称/ID |
去排查,注意镜像软件的版本问题。这里的mysql版本是9.1.0
docker网络
docker网络有四大模式,分别是bridge,host,none,container,下面简要的说明一下这四种模式。
网络模式 | 说明 |
---|---|
bridge | 为每一个容器分配一个ip ,并且将容器连接到指定的虚拟网桥,创建docker 网络的时候默认是这个模式,这个模式也是最常用的模式 |
host | 容器不会虚拟出自己的网卡,配置自己的ip ,而是和使用宿主机的ip 和端口 |
none | 容器有独立的network namespace ,但是并没有进行任何的网络配置。 |
container | 新创建的容器不会创建自己的网卡和配置自己的ip ,而是和一个指定的容器共享ip、端口范围等。 |
bridge 模式
bridge 模式的特点
docker 服务默认会创建一个 docker0 网桥(其上有一个 docker0 内部接口),该桥接网络的名称为 docker0,他在内核层联通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。docker 默认指定了 docker0 接口的 ip 地址和子网掩码,让主机和容器之间可以通过网桥互相通信。
查看宿主机的网络配置
1 | ifconfig |
在这里,可以看到一个 docker 服务创建的 docker0 网桥。
- 整个宿主机的网桥模式都是 docker0,类似交换机有一堆的接口,每个接口叫 vethXXX(virtual ethernet 虚拟以太网),在本地主机和容器内分别创建一个虚拟接口,并且让他们彼此联通。
- 每个容器实例内部也有一块网卡,每个接口叫 eth0
- docker0 上面的每个 veth 匹配某个容器实例内部的 eth0,一一对应的关系。
这样宿主机将每一个容器实例都连接到这个内部虚拟网络上,两个容器在同一个网络上,会从网关拿到各自分配到的 ip 地址,此时这两个容器的网络是互通的。
下面我们来验证一下是不是这样的。
下面启动两台 tomcat 服务器
1 | docker run -d -p 8081:8080 --name t1 billygoo/tomcat8-jdk8 |
然后我们使用如下命令查看 ip 的相关信息
1 | ip addr |
看最后的两条
7: vethc573879@if6
9: veth90dfb51@if8
然后我们分别进入到两台容器实例的内部
1 | docker exec -it container_name bash |
⚠️ 在容器内部执行命令
1 | ip addr |
然后这个时候你就会看到
6: eth0@if7:
同理,第二台容器实例也是这样的。
查看默认启动容器的网络说明:这个小结所创建的两个容器实例没有做任何特殊的网络配置
1 | docker inspect container_name | tail -n 20 |
可以看到,这里的网络模式为bridge
模式,并且分配的ip地址是 172.17.0.2
,网关是 172.17.0.1
1 | "bridge": { |
下面再查看一台容器的ip分配情况,
1 | "bridge": { |
很明显第二个容器也是bridge
模式,并且分配的ip是172.17.0.3
,网关一致。
查看docker默认的网络
1 | docker network ls |
列出所有docker网络
1 | NETWORK ID NAME DRIVER SCOPE |
这里有一个名字叫bridge的网络,下面看一下它具体的网络配置
1 | docker inspect network bridge | tail -n 30 |
执行这条指令之后,可以看到如下的信息。可以看到,和我们之前看到的网络配置信息对应上了。
1 | "IPAM": { |
值得注意的一点是,docker默认容器实例的网络分配的ip是动态调整的,生成的ip并不会固定。比如说,当一个mysql容器实例挂掉之后,它对应的ip地址会被docker回收,然后这个时候系统后续又创建了几个容器实例,后续之前的mysql容器再启动之后,ip可能是不再是之前的ip了。eg:下面我将停止一个容器,然后又启动一个新的容器实例,然后执行一条命令。
1 | docker inspect network bridge | tail -n 30 |
显然,之前的ip已经被占用了,这个时候被停止掉的容器再重新启动,默认分配到的ip就不会之前的ip了
1 | "Containers": { |
host 模式
host 模式与宿主机共用网络配置。
host 模式下,指定端口不再起作用,并且允许容器实例的时候会出现一个警告:
WARNING: Published ports are discarded when using host network mode
意思是说,在使用主机网络模式时,已发布的端口将被丢弃。
1 | docker run -d --network host -p 8083:8080 --name t3 billygoo/tomcat8-jdk8 |
从上图可以看到,容器是已经启动了。既然是共用宿主机的配置,猜测应该访问宿主机的 8080 端口即可访问到 tomcat
再次进行验证。进入容器 t3 容器的内部,查看网络配置
1 | docker exec -it t3 bash |
可以看到,使用ip addr
指令出现的信息与宿主机是一致的。
这里就完美的验证了 host 模式的特点,与宿主机共享一份网络配置。
none 模式
当把容器实例的网络模式设置成none
的时候,就相当于禁用掉了这个容器对外的网络。docker 只会给这个容器创建一个本地回环测试的 lo
网络。
1 | docker run -d --network none --name t4 billygoo/tomcat8-jdk8 |
进入到容器中使用 ip addr
指令,发现只存在一个 lo
本地回环测试网络。
container 模式
当设置一个容器的网络模式为容器模式的时候,这个容器的网络会与指定的容器共用一个 网络配置。
下面启动两个 alpine 实例进行验证
1 | docker run -it --name alpine0 alpine /bin/sh |
1 | docker run -it --name alpine1 --network container:alpine0 alpine /bin/sh |
在宿主机上查看相关的网络信息。
1 | ip addr |
可以看到,只虚拟出了一个 veth
15: veth9568a07@if14:
下面再进入到 alpine0 和 alpine1 里面去看一下相关的网络配置。
可以看到,两个容器实例的网络配置是一样的。
当我们关掉 alpine0 之后,alpine1 的网络是这样的。
可以看到 alpine1 里面的 14: eth0@if15
也被移除了
自定义网络
在实际情况下,我们需要对网络进行管理,这样我们的服务才能更好的维护。
对于网络的自定义,之前 docker
网络有一个比较值得注意点,也就是容器实例的 <font style="color:#DF2A3F;">ip</font>
并不固定,可能这次启动时这个 ip
,下一次启动可能就不是这个 ip
了。如果在我们服务里面,不同容器之间使用到了其他容器的 ip 进行容器之间的通信。很大概率后续会导致服务连接错误等状况。docker
也考虑到了这一点。所以在实际情况中,我们可以手动的创建一个网络,然后将容器实例放到我们创建的自定义网络上。(可以考虑手动分配 ip
地址),也可以让他自动分配 ip
地址。然后容器实例之间通过服务名(具体提现为容器名字)来通信,这样就不会因为 动态 <font style="color:#DF2A3F;">ip</font>
地址而出现服务错误的问题。
下面来验证一下
1 | docker network create cles_net |
然后我们创建两个容器,将其连接到我们新创建的 cles_net
网络上cles_net
是自定义的网络名称,可以自己取。
1 | docker run -d --network cles_net -p 8081:8080 --name t1 billygoo/tomcat8-jdk8 |
1 | docker run -d --network cles_net -p 8082:8080 --name t2 billygoo/tomcat8-jdk8 |
然后分别进入到这两个容器内互相 ping 对方的容器名
可以看到,完全是可以互相联通的。
⚠️如果我们没有指定容器连接上自定义网络的话。他们之前 ping 容器名是 ping 不通的。虽然他们是在同一个网段下面的。
比如我们这样启动两个容器实例
1 | docker run -d -p 8081:8080 --name t1 billygoo/tomcat8-jdk8 |
然后两台容器之间检测连通性。
docker-compose
基本概念
在之前的 mysql 容器实战的时候。我们要先创建一个自定义网络net1
,然后使用docker run
指令去启动我们的 mysql 容器,然后再用docker run
指令去 启动我们的phpadmin
。这个启动顺序是不可以乱来的,也就是说要让我们的服务正常的启动并且运行,是要有一个合理的启动顺序的。
但是如果后续容器多了起来,这样操作就非常的繁琐了。并且不利于维护。docker-compose 让我们基于一个 yml 文件去管理我们一整个容器集群的启动。
安装 docker compose
docker compose 的安装官方说的已经非常详细了,并且不同的操作系统提供了不同的安装方式,之前已经安装了 docker desktop,就不需要再安装 docker compose 了,他已经集成到里面了
官方示例实战
在做这个实验的时候,踩了很多坑,包括不限于:docker compose 的安装、容器内网络问题、容器编排完毕之后镜像拉取失败从而导致整个服务起不来……
那么就开始吧。
- 首先创建一个工作目录
1 | mkdir composetest |
- 然后创建一个 python 文件
这个 python 文件开启了一个很简单的 web 服务器,用的是 flask ,然后使用 redis 去记录某台主机访问这个网站的次数。
1 | import time |
- 接下来就是把我们的服务容器化了。
编写 python 的依赖文件requirements.txt
这里只是一个示例,所以依赖不多,就一个 flask 和 redis 包
1 | flask |
在这里踩了很多坑
- alpine 容器内部拉取相关依赖的时候超时。跑了几千秒都没搞好。
- 然后就是 pip 拉取相关的 python 依赖超时
总结,当出现这种情况的时候,停止构建,在 Dockerfile 中添加切换国内镜像源的脚本。下面文件的第五行和第八行是新增的。
1 | FROM python:3.10-alpine |
- 编写 yml 文件
1 | # yaml 配置 |
- 文件都准备好了之后,就可以开始启动 docker compose 了
在工作目录下面输入指令
1 | docker compose up -d #如果是旧版本的docker compose,使用docker-compose up -d |
坑点:由于本地没有python:3.10-alpine
这个镜像,这个时候 docker 会到 docker hub 去拉取这个镜像,很有可能会因为网络问题导致镜像拉取失败,从而 docker compose up 失败。
解决方案,先把这个镜像拉取到本地,然后再执行 docker compose up -d
指令
不出意外的,成功的将服务跑起来了
并且访问 localhost:5000 能看到如下的界面,当刷新界面的时候,计数器就会增加。
值得注意的是,即使我们没有在任何地方指定这个服务集群的网络,docker 会为我们创建一个默认的网络,当我们使用指令
1 | docker compose down #关闭服务 |
这个时候 docker 会删除掉这个默认网络。