缘起
我自建的网站几乎全部是用Docker部署的,在跟朋友聊起我是如何搭建这些网站时,我发现很多人都表示听说过Docker或者容器,但并不知道它们到底是什么。
其实,我在最开始使用Docker时,接触到很多容器相关的概念,但由于没有实践过,对这些概念也是一头雾水。如今已经用了很长时间的容器技术,就想总结一下相关的知识,希望能够帮助到其他人。
容器化
什么是容器化
容器化是一种虚拟化技术,它可以将应用程序及其依赖打包在一起,以便在不同的环境中运行。容器化的应用程序可以在任何地方运行,包括开发人员的笔记本电脑、物理服务器、虚拟机、容器集群和公有云等。
为什么需要容器化
直接看容器化的概念,可能还是不好理解。我们用一个例子来说明为什么需要容器化,这样容器化的概念就会很清晰了。
首先,如果你只是一个程序或者服务的使用者,不关心这个程序或服务是怎样在机器上运行的,那么你根本不必了解容器化的概念。
其次,如果你运行的程序或服务只是一个简简单单的,只要能在你的电脑上运行就行了的程序,那么你也不必了解容器化的概念。
所以,这里假设你是一个开发者(当然,你不必真的在开发程序,因为对于大多数新手而言,他们并没有开发什么程序,他们只是在使用从开源网站上或其他地方搞到的程序),你有一个想运行的程序,这个程序可以提供一些服务,你想在不同的地方都能获取这些服务。举个例子,你用某个流行的网站框架自建了一个网站,你希望自己能够在不同地方的不同设备上访问这个网站,也希望别人能访问你的网站。这时,你将这个网站部署到了自己的电脑上,假设你的网络运营商给你分配了静态IP、开放了端口,你就可以将你的网站在公网上开放,这样你就可以在任何能连接到公网的地方访问这个网站了。
过了一段时间,你购买了一个域名,并用网页服务器(如Nginx或者Apache)将这个域名绑定到了你的网站上,这样你就可以通过域名访问你的网站了。
又过了一段时间,你发现你在你的网站上部署的服务越来越多,你的网站也越来越受欢迎,访问量越来越大,你电脑孱弱的性能已经无法满足这么大的访问量了,你决定购买一台更强的电脑或者服务器,将你的网站迁移过去。
于是你组装了一台服务器,装上了Linux系统,开始迁移网站。你把原来电脑上的网站文件复制到了新的电脑上,然后开始运行,发现网站根本无法运行。因为你的网站有很多依赖项,例如Python库和一些乱七八糟的库,你只能一个一个安装这些库。然后会发现很多库的默认版本都已经比你在原来的电脑上安装的版本新了,新库根本不兼容你原来的旧代码,你只能上网找旧版本的库,一个个安装。幸运的话,你可以顺利安装上旧版本的库;要是不幸运的话,你可能发现你的操作系统也依赖某些库,你安装旧版本后操作系统直接崩溃。
这还没完,即使你成功解决了依赖库的问题,你在原来电脑上为你的网站配置的数据库、网页服务器等等,全部需要重新配置。
这时你可能会想,要是能把原来电脑上的网站、数据库、网页服务器,以及它们的依赖项和运行环境,全部打包起来,然后在新电脑上可以直接运行就好了。如果你之前用过虚拟机的话,你可能会想,这不就是创建一个和原来的电脑相同的虚拟机,然后将虚拟机迁移到新电脑上就行了吗?
但是虚拟机太笨重了,因为虚拟机包含了一个完整的操作系统,一般都会占用很多资源,启动速度也很慢。所以我们不想创建整个操作系统,我们只想创建一个和原来的程序相同的运行环境。这,其实就是容器化。
如何容器化
现在我们的目标已经很明确了,我们要将一个程序及其依赖项和运行环境打包起来,然后在不同的地方运行这个打包好的程序。所以,简单来说,容器化分两步:1. 打包程序及其依赖项和运行环境;2. 在机器上隔离出独立的运行环境,运行打包好的程序。
打包
这一步比较好说,只需要将程序及其依赖项和运行环境打包起来就行了。打包好的程序及其依赖项和运行环境,我们称之为镜像(Image)。
隔离环境
隔离环境的技术依赖于操作系统的特性,在Linux系统中,隔离环境依赖于三项技术:命名空间(Namespace)、控制组(Control Group)和chroot:
- 命名空间:命名空间可以隔离进程的视图,使得进程只能看到自己的视图,而看不到其他进程的视图。
- 控制组:控制组可以限制进程的资源使用,例如CPU、内存、磁盘IO等。
- chroot:chroot可以更改进程的根目录。
这三项技术的详细介绍超出了本文的范围,有兴趣的读者可以自行搜索。
Windows系统也有容器技术,但和Linux系统有所不同。在Windows系统的Docker Desktop中,容器化的技术依赖于Hyper-V虚拟机。
容器与虚拟机的区别
从上面的描述可以看出,容器和虚拟机都提供了隔离环境的功能,二者都可以称为沙盒(Sandbox)技术。但二者又有很大区别:
- 容器是在操作系统层面上实现的,提供的隔离是进程级别的,必须和宿主机共享操作系统内核
- 虚拟机是在硬件层面上实现的,提供的隔离是操作系统级的,可以有自己的操作系统内核
由于容器和宿主机共享操作系统内核,所以容器的启动速度(秒级)比虚拟机(分钟级)快得多,容器的资源占用也比虚拟机少得多。
Docker
容器化需要的三项技术分别在1979年(chroot)、2002年(namespace)和2007年(cgroup)就已经出现了,但是这三项技术的初衷并不是容器化。直到2013年,Docker横空出世,将这三项技术结合起来,容器化开始流行起来。
Docker的组成和使用都很简单,如下图所示:
Docker的组成
图中包含了Docker的三个核心概念:镜像(Image)、容器(Container)和仓库(Repository)。
-
镜像:镜像是一个只读的模板,它包含了运行程序所需要的所有东西,包括代码、运行环境、库、环境变量和配置文件等。镜像是容器的基础。
-
仓库:仓库是用来存放镜像的地方,可以理解为镜像的集合。仓库分为公有仓库和私有仓库,公有仓库是开放的,任何人都可以上传和下载镜像;私有仓库是私有的,只有拥有者可以上传和下载镜像。
-
容器:容器是镜像的运行实例,它包含了镜像以及运行时所需要的东西,包括文件系统、系统环境、网络配置等。容器是镜像的运行时状态。
Docker的使用(单个容器)
Docker的使用有两种情况:一种是使用别人的镜像,另一种是自己制作镜像。
使用别人的镜像
使用别人的镜像非常简单,只需要两步:
-
下载镜像:使用
docker pull
命令下载镜像,例如:1
docker pull ubuntu:latest
这个命令会从Docker Hub下载一个名为
ubuntu
的镜像,标签latest
表示拉取的镜像是最新的版本。 -
运行容器:使用
docker run
命令运行容器,例如:1
docker run -it --rm ubuntu:latest /bin/bash
上面的命令会运行一个名为
ubuntu
的容器,标签为latest
,并且进入容器的bash终端。
使用自己的镜像
使用自己的镜像需要三步:
-
编写Dockerfile:Dockerfile是一个文本文件,它包含了一系列命令,这些命令用来构建镜像。例如:
1 2 3 4 5
FROM python:3.12 WORKDIR /app COPY . /app RUN pip install -r requirements.txt CMD ["python", "app.py"]
上面的Dockerfile中,
FROM
命令表示基础镜像是python:3.12
,WORKDIR
命令表示工作目录是/app
,COPY
命令表示将当前目录下的所有文件复制到/app
目录下,RUN
命令表示安装requirements.txt
中的依赖,CMD
命令表示容器启动时运行的命令。 -
构建镜像:使用
docker build
命令构建镜像,例如:1
docker build -t myapp .
上面的命令会在当前目录下构建一个名为
myapp
的镜像。 -
运行容器:使用
docker run
命令运行容器,例如:1
docker run -d -p 5000:5000 myapp
上面的命令会运行一个名为
myapp
的容器,并且将容器的5000端口映射到宿主机的5000端口。
Docker的使用(多个容器)
通常情况下,我们在部署一个服务时往往需要用到不止一个容器,例如我们要自建一个网盘服务,我们可能需要一个容器用来运行网盘服务端,还一个容器用来运行数据库服务。而一些复杂的服务可能需要更多的容器。
同时部署和管理多个容器的技术称为容器编排(Container Orchestration),Docker提供了一个名为docker-compose
的工具来实现容器编排。
docker-compose安装
在Docker客户端发布之初,Docker并不具备编排容器的功能,于是有开发者开发了docker-compose
工具来实现容器编排,这个版本的docker-compose
被称为docker-compose
的第一版(v1)。
后来Docker公司发布了Docker客户端的新版本,这个版本的Docker客户端具备了编排容器的功能,称为第二版(v2)。
在Ubuntu 22中,docker-compose
可以用apt
包管理器安装,使用apt search docker-compose
命令可以在apt
的源中可以看到有三个版本的docker-compose
:
|
|
第1个是docker-compose
的第一版,第2个和第3个都是docker-compose
的第二版。
- 第1个是第一版,我们就不考虑了
- 第2个是作为
docker-cli
的插件,使用时的命令是docker compose
- 第3个是独立的工具,使用时的命令是
docker-compose
我们一般选择安装第3个,因为网上很多教程里都用docker-compose
命令,而不是docker compose
命令。
docker-compose使用
docker-compose
使用一个名为docker-compose.yml
的文件来定义多个容器的配置,例如我们用Nextcloud部署个人云网盘服务时,使用的docker-compose.yml
文件如下:
|
|
这个文件定义了两个服务:db
和app
,db
服务使用mariadb
镜像,app
服务使用nextcloud
镜像。db
服务和app
服务都使用了nextcloud_network
网络。
我们可以使用docker-compose
命令来启动这两个服务:
|
|
如果要停止这两个服务,可以使用docker-compose
命令来停止:
|
|
Kubernetes / K8s
上面介绍的docker-compose
工具是Docker公司推出的,它的功能比较简单,适合只在单个电脑或者服务器上使用的小型项目。对于包含数个计算机集群的大型项目,docker-compose
的功能就显得有些不足了,这时就需要使用Kubernetes(简称K8s)。
Kubernetes(简称K8s,因为K
和s
之间有8个字母)是一个开源的容器编排引擎,它可以自动化地部署、扩展和管理容器化的应用程序,功能非常强大。
需要指出的是,容器化的标准由Open Container Initiative(OCI)制定,Docker只是OCI的一种实现。Kubernetes也是OCI的一种实现,它之前使用Docker作为容器运行时,但现在已经不再依赖Docker,而是使用containerd作为容器运行时。
我暂时也还没有用到Kubernetes,所以这里就不再详细介绍了。