镜像和容器是docker中最基础的概念,镜像可以理解为包含应用程序以及其相关依赖的一个基础文件系统,在其启动过程中,以只读的方式被用于创建容器的运行环境,本质上是基于UnionFS文件系统的一组镜像层依次挂载而得,每个镜像层包含的是对上一层镜像的修改

获取镜像

最常见的获取镜像的方式是从镜像仓库拉取,即使用docker pull

$ docker pull python
Using default tag: latest
latest: Pulling from library/python
699c8a97647f: Pull complete 
86cd158b89fd: Pull complete 
a226e961cfaa: Pull complete 
4cec535da207: Pull complete 
225fdd30e1a3: Pull complete 
356a16c6c201: Pull complete 
c1615dab98ef: Pull complete 
8b4436f8e17c: Pull complete 
c20627d9f29f: Pull complete 
Digest: sha256:4d25a30d2943e5feb9c571b763936c843d7a6f45216f508bfe99741067f4ca06
Status: Downloaded newer image for python:latest
docker.io/library/python:latest

当没有使用镜像标签时,docker会默认使用latest

镜像被拉取后,就存放到本地,接受当前docker实例管理,通过docker images即可看到

Docker Hub是docker官方建立的中央镜像仓库,除了普通镜像仓库的功能外,内部还有更加细致的权限管理,可以通过docker search搜索其中镜像

$ docker search ubuntu

通过docker inspect可以获取镜像更详细的信息

$ docker inspect ubuntu
[
    {
        "Id": "sha256:27941809078cc9b2802deb2b0bb6feed6c236cde01e487f200e24653533701ee",
        "RepoTags": [
            "ubuntu:jammy",
            "ubuntu:latest"
......

docker inspect 的结果中可以看到关于镜像相当完备的信息

使用docker rmi可以删除镜像,参数是镜像名称或ID

$ docker rmi golang:1.8.3
Untagged: golang:1.8.3
Untagged: golang@sha256:32c769bf92205580d6579d5b93c3c705f787f6c648105f00bb88a35024c7f8e4

运行管理

可以通过docker create来创建容器

$ docker create --name nginx nginx:latest
fb0125e4f477c5fa9046db8a134eb9f0c8a7610508c690947c86f9c1149f2413

Docker会根据所给出的镜像创建容器,在控制台中会打印出Docker为容器所分配的容器ID,此时容器处于Created状态,之后对容器的操作可以通过容器ID或者其缩略形式进行

处于Created状态的容器,其内部的应用程序还未启动,通过docker start启动

$ docker start nginx
nginx

由于为容器指定了名称,所以可以直接制定名称

当容器启动后,其中应用就会运行出来,容器的几个生命周期,也会绑定到这个应用

docker run可将docker createdocker start合为一步

$ docker run --name centos centos:latest

加上-d可以让容器在后台运行:

$ docker run --name nginx -d nginx:latest
f4b404f3062aacb4640abcfcc1ff5816ed76b96cf64f88d541c93c602a082b7a

使用docker ps可以列出处于运行中的容器,如果要列出所有状态的容器,要增加-a选项

$ docker ps

docker stop可以停止正在运行的容器

$ docker stop nginx

使用docker rm完全删除容器,正在运行的容器默认情况下不可删除

$ docker rm nginx

使用docker exec让容器运行给出的命令:

 docker exec nginx more /etc/hostname
::::::::::::::
/etc/hostname
::::::::::::::
f4b404f3062a

可以打开容器的bash,来实现对容器内虚拟环境的控制

$ docker exec -it nginx bash
root@f4b404f3062a:/# 

上述命令要加上-it选项,-i表示保持输入流,只有使用它才能保证控制台程序能够识别命令,-t表示启用一个伪终端,形成与bash的交互

使用docker attach用于将当前输入输出流连接到指定的容器上

$ docker attach nginx

可以理解为将容器中主程序转为了前台运行

容器网路

打通容器间的网络,使其能够互相通讯:

$ docker run -d --name mysql -e MYSQL_RANDOM_ROOT_PASSWORD=yes mysql
$ docker run -d --name webapp --link mysql webapp:latest

使用--link选项进行配置,即可实现容器间的网络通讯

网络打通不意味着可以任意访问被连接容器中的任何服务,Docker为容器访问增加了一套安全机制,只有容器自身允许的端口,才能被其他容器访问

使用--expose可以在容器创建时进行定义暴露的端口

$ docker run -d --name mysql -e MYSQL_RANDOM_ROOT_PASSWORD=yes --expose 13306 --expose 23306 mysql:5.7
f9fd4c57b996247b62b2e20b0bccb3c0f0f255ad12535602ff31cfdad9458843

上述暴露了13306和23306两个端口,可以在docker ps中看到这两个端口已经打开

docker inspect中的Networks字段也可以看到网络的相关信息

$ docker inspect mysql
[
    {
        "Id": "f9fd4c57b996247b62b2e20b0bccb3c0f0f255ad12535602ff31cfdad9458843",
        "Created": "2023-02-07T14:01:45.754100581Z",
        "Path": "docker-entrypoint.sh",
	.	.........
            "Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "b590cb0b2c9aee999cfe6ee55353da7771544a5f4db0b20f6f9dccf332ebe498",
                    "EndpointID": "cea559cd785577a87ec86fab0f0576c410383b0557db037f42553398a461a9c5",
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.3",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:11:00:03",
                    "DriverOpts": null
                }
            }
        }
    }
]

上述可以看到mysql容器在bridge网络中分配的IP地址,自身的端点和MAC地址,bridge网络的网关地址信息

在没有明确制定容器网络时,容器会连接到bridge网络中

在docker中可以创建网络,形成自己定义的虚拟子网

$ docker network create -d bridge individual
27e2b2b893b89f2b8a8474d925855e00edfb4ca27a3c9102392b921572f2216f

-d选项可以为新的网络指定驱动的类型,其值可以是bridge(默认),也可以是其他类型

使用docker network ls可以查看docker已经存在的类型:

$ docker network ls
NETWORK ID     NAME           DRIVER    SCOPE
b590cb0b2c9a   bridge         bridge    local
95aa02052f1e   host           host      local
27e2b2b893b8   individual     bridge    local

创建容器时,可以通过--network来指定容器加入的网络,一旦被指定,容器便不会默认加入到bridge这个网络中了

$ docker run -d --name mysql57 -e MYSQL_RANDOM_ROOT_PASSWORD=yes --network individual mysql:5.7
634883bd92f2c57cb6e3190eb6fb2112af9ac7b8d5ede66233ac741b4fa62796

此时再查看其Network字段:

"Networks": {
                "individual": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": [
                        "634883bd92f2"
                    ],
                    "NetworkID": "27e2b2b893b89f2b8a8474d925855e00edfb4ca27a3c9102392b921572f2216f",
                    "EndpointID": "e21cb42cbfb537b09e286fc659d2d3b4d2aaf6a55509380fb8efbd87afe296fa",
                    "Gateway": "172.19.0.1",
                    "IPAddress": "172.19.0.2",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:13:00:02",
                    "DriverOpts": null
                }
            }
...

可见容器所加入的网络已经变成了individual网络,如下可以将其他容器连接到这个网络

$ docker run -d --name webapp --link mysql --network individual webapp:latest

使用-p可以进行端口映射

$ docker run -d --name nginx -p 80:80 -p 443:443 nginx
213cb28960cdfee22d6907b9534b9bb99a2e767e770c98c5bbecf0c985c12188

存储数据

使用-v选项来指定内外挂载的对应目录或文件,挂载文件到容器

$ docker run -d --name nginx -v /webapp/html:/usr/share/nginx/html nginx
d8653640c428f17214f88d50f2eec3e194d0ea8f357cd4bff4174e00706be124

形式是 -v <host-path>:<container-path>--volume <host-path>:<container-path>,其中 host-path 和 container-path 分别代表宿主操作系统中的目录和容器中的目录,为了避免混淆,Docker 这里强制定义目录时必须使用绝对路径,不能使用相对路径

之后就可以看到宿主操作系统中的文件出现在了容器中的目录

在挂载的宿主机目录下创建index.html,会发现容器挂载到目录下也出现了该文件

$ docker exec nginx ls /usr/share/nginx/html
index.html

通过docker inspect可以看到挂载信息:

   "Mounts": [
            {
                "Type": "bind",
                "Source": "/webapp/html",
                "Destination": "/usr/share/nginx/html",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],

挂载信息中可以看到一个RW字段,这表示挂载目录或文件的读写性

Docker 还支持以只读的方式挂载,通过只读方式挂载的目录和文件,只能被容器中的程序读取,但不接受容器中程序修改它们的请求。在挂载选项 -v 后再接上 :ro 就可以只读挂载

-tmpfs可以挂载临时目录:

$ docker run -d --name webapp --tmpfs /webapp/cache webapp:latest

此处利用内存来存储数据,由于内存不是持久性存储设备,所以带给Tmpfs Mount的特征就是临时性挂载

-v还可以定义数据卷,其本质依然是宿主操作系统上的一个目录,不过放在容器内部,接收docker管理

$ docker run -d --name golang -v /storage golang:alpine
c15750feba4aec5040491f5530eb4eaef7bd92c38ae23f1b8e9172dc4a88cdde

查看其挂载信息:

       "Mounts": [
            {
                "Type": "volume",
                "Name": "73eb66e64795352afd6e81384147b7372450e234938fbb71a3c07f1499502950",
                "Source": "/var/lib/docker/volumes/73eb66e64795352afd6e81384147b7372450e234938fbb71a3c07f1499502950/_data",
                "Destination": "/storage",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],

Type类型为volume,如果希望数据在多个容器间共享,利用数据卷可以在保证数据持久性和完整性的前提下完成自动化操作

使用docker volume create独立创建数据卷:

$ docker volume create appdata
appdata

通过docker volume ls可以列出当前已创建的数据卷:

$ docker volume ls
DRIVER    VOLUME NAME
...
local     appdata
...

同理,删除数据卷:

$ docker volume rm appdata 
appdata

使用--mount可以更丰富地配置挂载,使用CSV格式来定义多个参数

sudo docker run -d --name webapp webapp:latest --mount 'type=volume,src=appdata,dst=/webapp/storage,volume-driver=local,volume-opt=type=nfs,volume-opt=device=<nfs-server>:<nfs-path>' webapp:latest