Docker 与 容器
Docker 是基于容器技术实现的,由于 Docker 的火热流行,可能很多人会认为容器是 Docker 独有的技术。
实际上,容器技术最开始是基于 Linux Container(简称 LXC)技术实现的,通过内核提供的 Namespace
和 Cgroup
机制,实现了对应用程序的隔离以及物理资源的分配。后来随着 Docker 的发展壮大,容器技术越来越被更多的公司接受,才发展出了标准容器技术。
容器技术和虚拟机(虚拟化)技术有点像,它们两者都提供了环境隔离的功能。不同的是。容器是运行在操作系统上的一个进程,它和其他应用程序是共享内核的,由操作系统提供虚拟化隔离功能;而虚拟机则是完完全全另起了个操作系统,将环境隔离的更加彻底。
Docker 在容器基础上发展出了一个完善的生态系统,它将容器视为一种打包格式,将应用程序所需的一切,比如依赖库、运行时环境等都集合在了在一起,使得一次构建,到处运行。
这也是 Docker 为什么能发展壮大的原因,它将开发与运维很好的融合在一起。开发人员可以很轻松的构建、打包、推送和运行应用程序。而且还允许我们将容器视为部署单元,以模块化的方式发布,降低了系统的运维管理难度。
Docker 基本概念
镜像和容器是 Docker 的核心概念,掌握了它们,也就明白了 Docker 在做什么事了。镜像是一个模板文件,就像我们以前安装系统时用到的光盘,有了这张光盘,就可以随时将应用程序跑起来,而不需要自己手动的去构建应用程序所需要的一切,因为它已经在镜像里安排的明明白白了。
而容器是镜像的运行实例,我们可以把镜像看成是一个个的构建块,容器根据这些构建块搭建起了一个隔离的,拥有整个包的应用程序。每一个容器都是一个标准化单元,确保了在不同机子上也能拥有一致的行为。
Docker 的镜像是一个分层的文件系统,通过一层层的组合,使得我们可以复用这些不同粒度的镜像文件,像 Alpine 镜像、Ubuntu 镜像等基础镜像文件。
Docker 也是一个开放的程序构建运行平台,当我们将一个镜像构建完成后,就可以将其上传到一个集中存储、分发的仓库了。这样,每当我们或其他人想要基于镜像构建程序的时候,就可以从仓库统一拉取,进行后续动作。
Docker 的仓库就像 app 的应用商店一样,制作人负责将做好的镜像上传上去,其他人只需要根据下载地址 Download 下来使用就行了。像 Docker 官方的 Docker Hub,还有阿里云的镜像仓库,都提供了此功能。当然,我们也可以搭建私有仓库,进行访问控制。
Docker 的搭建
好了,是时候开始动手搭建属于我们的 Docker 服务了,这里我们的环境为 Ubuntu 操作系统。
卸载旧版本
如果我们之前安装过 docker ,先卸载:
sudo apt-get remove docker docker-engine docker.io containerd runc
更新 apt 包索引并且添加使用 HTTPS 传输的软件包以及 CA 证书:
sudo apt-get update
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release
添加 Docker 官方的 GPG 密钥:
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
设置稳定存储库:
echo \
"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
安装 Docker 引擎:
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io
通过运行 hello-world 镜像验证 Docker Engine 是否已正确安装:
sudo docker run hello-world
如果容器运行打印了消息,则表示安装好了。
Docker 的常用命令:
容器生命周期管理: run、start/stop/restart、kill、rm、pause/unpause
容器操作:ps、inspect、top
镜像仓库:login、pull、push、search
本地镜像管理:images、rmi、tag、build、history
例如,当我们需要运行一个容器时,则可以执行:
docker run nginx:test
当我们掌握这些常用命令后,就可以直接在 Docker 上构建起我们的容器程序了。
数据卷
当我们使用上面的命令 run 了一个容器,后面对其 kill 重启后,就会发现原来在容器里的相关数据、文件操作都会丢失,就好像回到最开始的状态一样。
这是因为容器退出后并不能对镜像进行修改操作,所以重新加载后,还是使用原来的镜像构建。
如果我们想要对数据进行持久化,不随容器结束而结束,那我们需要将宿主机的某一文件目录挂载到容器里,通过映射的方法来实现 Docker 的持久化。在 Docker 里提供了三种方法来实现目录的挂载:
volumes
当我们创建一个volume
时,将会在 Docker 的主机上创建一个相对应的目录。这个目录就是用来映射到容器中的。后续容器里对此目录的操作都会反映到主机上,因此也就实现了 Docker 的持久化。
可以使用 docker volume create
来创建卷,一个卷可以同时给几个容器使用,这意味着容器之间可以利用同一个卷来实现数据的共享.
bind mounts
bind mounts
只需要存在一个真实的目录即可挂载到容器中,当 bind 的目录不存在时会创建一个新的目录。绑定挂载非常高效,但它们依赖于主机的具有特定目录结构的文件系统,比如从 Linux 切换为了 Windows,就会使用不了了。
tmpfs
tmpfs 挂载
仅存储在主机系统的内存中,不会持久保存在磁盘上。容器可以使用它来共享简单状态或非敏感的信息。
我们可以通过下面的图来看看三种的区别:
我们重点来看下 volumes 的相关操作。
当我们使用 docker volume create
创建卷之后,默认会在 /var/lib/docker/volumes
里有其对应的目录结构。
接着,我们就可以在 run 命令里使用卷了,例如
docker volume create my-vol
docker run -d \
--name devtest \
-v my-vol:/app \
nginx:latest
这样的话,就会将卷挂载到容器对应的目录了,实现持久化了。
当我们不使用时,可以使用 docker volume rm my-vol
来删除卷。但如果我们删除容器,并不会同时删除卷,如果想在删除容器时也同时删除卷,则需要执行 docker rm -v my-vol
命令。
网络
由于 Docker 上的容器是相互隔离的,所以当我们想要进行容器之间的通信时,并不能简单的像进程通信那样。
Docker 在安装时,默认会为我们创建三种类型的网络,我们可以通过这个命令看到对应的网络类型:
docker network ls
NETWORK ID | NAME | DRIVER | SCOPE |
---|---|---|---|
a1a862a7c667 | bridge | bridge | local |
bec784838efe | host | host | local |
cdc833fa220a | none | null | local |
bridge 模式
Docker 在主机上会创建一个 docker0
的网桥,每当有容器要创建时,便会为容器分配一个独立的网卡,然后桥接到虚拟网桥上。
这其实就是一对虚拟网卡,一端放在容器里,另一端放在 docker0
网桥里。当一端有数据达到时,就会把数据包转发到另一端上,这就实现了网络通信。
在 Docker 里是允许我们自建网络的,比如使用下面的命令就可以创建一个 bridge 的网络了:
# 创建自定义网络
docker network create my-net
然后,我们在创建一个容器时,就可以这么使用了:
# 使用自定义网络
docker run -d --name tomcat-my-net --net my-net tomcat
host 模式
我们知道,Docker 使用了 Linux 的 Namespaces 来进行资源的隔离,包括了网络隔离。所以我们创在建一个容器时,一般会为容器分配一个独立的 Network Namespace 以进行网络隔离。
如果我们使用了 Host
模式,则不再分配 Network Namespace,而是和宿主机共用一个 Network Namespace。此时容器将不再拥有自己的虚拟网卡、IP 和端口,而是和宿主机共用一个 IP 和端口。
docker run -d --name tomcat-my-net --net=host tomcat
none 模式
使用 none 模式的容器拥有属于自己的 Network Namespace,但不做任何网络配置。它和宿主机以及其他容器是不互通的。如果需要和外部通信,则需要自定义网络驱动程序,自己添加网卡、配置 IP 等。
docker run -d --name tomcat-my-net --net=none tomcat
网络使用总结
- 当你需要多个容器在同一个 Docker 主机上进行通信时,自定义的桥接网络是最佳选择。
- 如果想直接让外界访问容器应用,去掉容器与 Docker 宿主机的网络隔离,则可以使用 host 模式。
- 如果容器安全要求高,不想与其他容器应用通信,则可以使用 none 模式。