Docker
目录
基础和原则
- 按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用 数据卷(Volume)、或者 绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。
- 容器基于镜像。类似于
运行的进程
基于编译代码得到的bin文件
一样 - 镜像是多层存储,每一层是在前一层的基础上进行的修改;而容器同样也是多层存储,是在以镜像为基础层,在其基础上加一层作为容器运行时的存储层。镜像所使用的分层存储的概念,除当前层外,之前的每一层都是不会发生改变的,换句话说,任何修改的结果仅仅是在当前层进行标记、添加、修改,而不会改动上一层。如果使用 docker commit 制作镜像,以及后期修改的话,每一次修改都会让镜像更加臃肿一次,所删除的上一层的东西并不会丢失,会一直如影随形的跟着这个镜像,即使根本无法访问到。这会让镜像更加臃肿。
常见操作
如何下载镜像?
docker pull ubuntu:20.04
如何推送镜像到远程库 ?
# 需要提前登录 通过执行 docker login 命令交互式的输入用户名及密码来完成在命令行界面登录 Docker Hub
$ docker tag ubuntu:18.04 username/ubuntu:18.04 #username 请替换为Docker 账号用户名 junfiredocker
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 18.04 275d79972a86 6 days ago 94.6MB
username/ubuntu 18.04 275d79972a86 6 days ago 94.6MB
$ docker push username/ubuntu:18.04
$ docker search username
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
username/ubuntu
如何彻底删除镜像?
docker image ls ; docker image rm
有可能删除不成功,原因见https://yeasy.gitbook.io/docker_practice/image/rm 的Untagged 和 Deleted
小节)
如何导入和导出镜像 ?
docker save
和docker load
,如果是镜像迁移应该直接使用 Docker Registry, Registry即注册服务器, 是管理仓库的具体服务器,每个服务器上可以有多个仓库,而每个仓库下面有多个镜像。从这方面来说,仓库可以被认为是一个具体的项目或目录。例如对于仓库地址docker.io/ubuntu
来说,docker.io
是注册服务器地址,ubuntu
是仓库名。
怎么启动一个容器 ?
docker run -t -i ubuntu:20.04 /bin/bash
# 启动一个 bash 终端,允许用户进行交互, 如果加-d 参数,表示在后台执行
如何进入容器(容器已经启动) ?
- 在使用 -d 参数时,容器启动后会进入后台。 推荐大家使用 docker exec 命令进入容器
$ docker run -dit ubuntu #启动容器
69d137adef7a8a689cbcb059e94da5489d3cddd240ff675c640c8d96e84fe1f6
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
69d137adef7a ubuntu:latest "/bin/bash" 18 seconds ago Up 17 seconds zealous_swirles
$ docker exec -it 69d1 bash
root@69d137adef7a:/#
怎么终止一个容器 ?
docker container stop
怎么删除处于终止状态的容器 ?
$ docker container rm trusting_newton
trusting_newton
# 如果要删除一个运行中的容器,可以添加 -f 参数, Docker 会发送 SIGKILL 信号给容器。
# 清理所有处于终止状态的容器
$ docker container prune
怎么启动一个已经终止的容器 ?
docker container start
# 终止状态的容器可以用 docker container ls -a
命令看到
怎么重启一个容器?
docker container restart
怎么把容器里的修改保存成一个新的镜像?
- 直接使用docker export , 然后再docker import ? 这样就没有历史记录信息,更好
docker commit
(慎用)- 用Dockerfile构建
如何导入和导出容器 ?
# 导出使用 docker export 命令
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7691a814370e ubuntu:18.04 "/bin/bash" 36 hours ago Exited (0) 21 hours ago test
$ docker export 7691a814370e > ubuntu.tar # 这样将导出容器快照到本地文件
# 导入使用 docker import 从容器快照文件中再导入为镜像
$ cat ubuntu.tar | docker import - test/ubuntu:v1.0
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
test/ubuntu v1.0 9d37a6082e97 About a minute ago 171.3 MB
# 此外,也可以通过指定 URL 或者某个目录来导入,例如
$ docker import http://example.com/exampleimage.tgz example/imagerepo
- 用户既可以使用
docker load
来导入镜像存储文件到本地镜像库,也可以使用docker import
来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。
挂载主机目录(或者创建数据卷后再挂载)
# 加载主机的 /src/webapp 目录到容器的 /usr/share/nginx/html目录
$ docker run -d -P \
--name web \
-v /src/webapp:/usr/share/nginx/html # 等同于这个命令 --mount type=bind,source=/src/webapp,target=/usr/share/nginx/html \
nginx:alpine
# 也可以创建一个数据卷,然后挂载到容器里。所谓数据卷本质上就是在主机专用的docker目录下创建一个新的目录
$ docker volume create my-vol
# 查看指定 数据卷 的信息
$ docker volume inspect my-vol
[
{
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
"Name": "my-vol",
"Options": {},
"Scope": "local"
}
]
# 载一个 数据卷 my-vol到容器的 /usr/share/nginx/html 目录
$ docker run -d -P \
--name web \
# -v my-vol:/usr/share/nginx/html \
--mount source=my-vol,target=/usr/share/nginx/html \
nginx:alpine
#在主机里使用以下命令可以查看 web 容器的信息
$ docker inspect web
"Mounts": [
{
"Type": "bind",
"Source": "/src/webapp",
"Destination": "/usr/share/nginx/html",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
外部访问容器(端口映射)
- 容器中可以运行一些网络应用,要让外部也可以访问这些应用,可以通过
-P
或-p
参数来指定端口映射。
# 使用 hostPort:containerPort 格式本地的 80 端口映射到容器的 80 端口
$ docker run -d -p 80:80 nginx:alpine
容器互联
# 创建一个新的 Docker 网络。
$ docker network create -d bridge my-net
# 运行一个容器并连接到新建的 my-net 网络
$ docker run -it --rm --name busybox1 --network my-net ubuntu /bin/bash
# 打开新的终端,再运行一个容器并加入到 my-net 网络
$ docker run -it --rm --name busybox2 --network my-net ubuntu /bin/bash
#下面通过 ping 来证明 busybox1 容器和 busybox2 容器建立了互联关系。
#在 busybox1 容器输入以下命令
$ ping busybox2
PING busybox2 (172.19.0.3): 56 data bytes
64 bytes from 172.19.0.3: seq=0 ttl=64 time=0.072 ms
64 bytes from 172.19.0.3: seq=1 ttl=64 time=0.118 ms
#用 ping 来测试连接 busybox2 容器,它会解析成 172.19.0.3。
#同理在 busybox2 容器执行 ping busybox1,也会成功连接到。
$ ping busybox1
PING busybox1 (172.19.0.2): 56 data bytes
64 bytes from 172.19.0.2: seq=0 ttl=64 time=0.064 ms
64 bytes from 172.19.0.2: seq=1 ttl=64 time=0.143 ms
下面是一个包含指定ip地址的方式,这种方式也是可以实现容器互联的
# 创建一个network
sudo docker network create -d macvlan --subnet=192.168.124.0/24 --gateway=192.168.124.1 -o parent=ens33 mynet
# 在这个网络里创建容器
sudo docker run --net=mynet --ip=192.168.124.121 -it --rm ubuntu /bin/bash
其它
在apple M1上构建x86_64 Docker镜像
- docker镜像能跨平台运行;只要系统架构一样,是可以使用相同的镜像的,x86的镜像只能在x86系统使用,arm的镜像只能在arm系统使用,docker镜像对容器而言只是模拟了一个环境,与宿主机的关系不大。Docker镜像是操作系统族相关的,外面所谓的build once run anywhere是有前提条件的,是在Linux这个操作系统族内才生效。