Docker精讲:基本安装,简单命令及核心概念
docker服务部署
docker是一个容器管理工具,其内部容器才是具体服务,所以我们在安装docker时不需要有太多定制内容,只需要通过yum安装即可
1. 更新系统包
#更新现有依赖包,防止现有依赖包版本过低影响docker安装
yum update
2. 安装依赖包
yum install -y yum-utils device-mapper-persistent-data lvm2
3. 添加Docker的yum源
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
#如果上面的官方docker源速度过慢,可以下载下面的阿里云docker源
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
4. 安装Docker
yum install docker-ce docker-ce-cli containerd.io
5. 配置docker镜像源
vim /etc/docker/daemon.json
#修改为如下内容
{
"registry-mirrors": [
"https://docker.registry.cyou",
"https://docker-cf.registry.cyou",
"https://dockercf.jsdelivr.fyi",
"https://docker.jsdelivr.fyi",
"https://dockertest.jsdelivr.fyi",
"https://mirror.aliyuncs.com",
"https://dockerproxy.com",
"https://mirror.baidubce.com",
"https://docker.m.daocloud.io",
"https://docker.nju.edu.cn",
"https://docker.mirrors.sjtug.sjtu.edu.cn",
"https://docker.mirrors.ustc.edu.cn",
"https://mirror.iscas.ac.cn",
"https://docker.rainbond.cc"
]
}
如果上面的镜像源不可用,可以进入如下文章查看最新的更新
docker镜像源来源
yum配置的镜像源是让yum具有下载docker的能力,不会影响docker拉取镜像的速度,加速docker下载镜像的过程需要配置docker镜像源。
6. 启动Docker并设置开机启动
systemctl enable docker
7. 验证Docker是否安装成功
docker --version
容器化介绍
容器化通过将被部署的应用程序,放入包含其依赖的所有环境的容器中。通过操作系统提供的某些能力,让容器环境同外部实现环境隔离,可以在一台主机中,独立于其他服务,部署在一个独立的虚拟环境中。
什么是容器?
我们可以把容器想象成一个虚拟机,其外在表现和虚拟机极其相似,比如说,容器通常由其自己的文件系统,有其自己的虚拟网卡,虚拟网卡和真实网卡之间通过网桥连接(类似于虚拟机NAT模式)。其部署环境也独立于真实环境之外。
容器和虚拟机的区别
虽然在外在表现和虚拟机类似,不过其底层实现和虚拟机却是天差地别。
虚拟机通过分配真实的硬件资源,包括CPU,内存,磁盘等,打造一个真实的硬件环境。这样虽然能够实现环境隔离,不过对于资源分配不好把控,是对资源的浪费。
比如说,如果我想部署一个Kafka和一个redis在两个主机中,如果通过虚拟机来进行环境隔离,Kafka内存波动较大,我就需要给Kafka极大的内存资源,不过当Kafka内存占用较小时,由于分配的是固定的内存资源。剩余的资源也无法给redis使用。
并且虚拟机是模仿真实的主机环境,真实主机具有的环境虚拟机一应俱全,这就导致会引入大量的当前服务无需的依赖,这也是对资源的浪费。
虚拟机镜像虽然也可以在不同平台之间保持一致运行环境,但在虚拟机镜像的迁移和管理方面会较为复杂。
那么容器化如何解决了这些问题呢?
首先,容器的隔离实现是通过直接调用linux内核中的核心方法,不使用linux各种发行版的方法(如CentOS,Ubuntu等等)。所以只要是基于Linux的操作系统,容器都可以提供相同的环境,并且迁移方便。
并且容器不分配实际硬件资源,其运行环境和普通的程序运行时一样,都是在真实主机环境中运行。不同的的是其会调用linux操作系统的功能来实现容器之间的隔离。
容器的隔离实现
一个容器的实现主要通过LInux操作系统,提供的命名空间和控制组来实现和外界环境的隔离,生成一个独立的虚拟环境。
命名空间(NameSpace)
命名空间是 Linux 提供的一种机制,它将系统的全局资源划分为独立的组,使每个进程只能看到和访问它自己的资源。Docker 使用命名空间来实现容器之间的隔离,使得每个容器都认为自己是独立运行的。常用的命名空间类型包括:
- PID 命名空间(PID Namespace):隔离进程 ID,允许进程在容器中拥有自己的进程树和 PID 1。
- 网络命名空间(Network Namespace):隔离网络设备、IP 地址、路由表等,使容器有自己的网络栈。
- 挂载命名空间(Mount Namespace):隔离文件系统挂载点,使进程有自己独立的文件系统视图。
- UTS 命名空间(UTS Namespace):隔离主机名和域名,使容器有自己的主机名。
- IPC 命名空间(IPC Namespace):隔离信号量、消息队列等进程间通信机制。
- 用户命名空间(User Namespace):允许进程有自己的用户 ID 和组 ID 映射,提供更细粒度的权限隔离。
- 时间命名空间(Time Namespace):隔离系统时钟,允许不同命名空间内的进程看到不同的时间。
说了这么多,命名空间到底是什么啊?
实际上命名空间就是linux操作系统内核中的一个结构体,我们上面提到的每个命名空间都有一个自己的结构体,其中保存了命名空间的信息。而我们将一个主机内容添加进命名空间,本质上就是将记录主机内容信息的一个结构体的一个属性,保存对应命名空间结构体的地址。我们以最常见的进程举例:
在操作系统中,保存进程信息的结构叫PCB(进程控制块),当我们在PID命名空间中创建进程时,操作系统会创建两个PID,分别是真实PID和容器PID,保存进程信息的PCB也会将一个属性保存当前命名空间结构体的地址,这证明当前进程数据当前结构体,当多个进程都保存相同的命名空间结构体时,他们就是一个命名空间的进程,可以互相访问。
PCB中还保存了另外一个结构体,用于保存当前进程的所有容器层级和容器PID以及其他信息等等。在命名空间中还可以创建另一个命名空间,一个进程可以保存在多个互相嵌套的命名空间中。所以进程会保存多个容器PID以及层级信息。
通过这两个结构体,进程可以完成通过命名空间实现环境隔离,以及真实PID和容器PID的映射。
再比如网络命名空间本质上也是一个结构体,并且真实网卡和容器内虚拟网卡由虚拟交换机和veth pair来实现真实网课和虚拟网卡的连接。其他的不一一细讲了,大概理解命名空间本质概念就好。
控制组(cgroups)
我们仍然使用Kafka和redis的部署案例。通过容器化部署,可以防止在Kafka低内存消耗时,分配给其的内存资源被浪费。不过在Kafka接收消息量较大时又会出现另一个问题,如果放任其无限制扩展内容,势必会导致redis没有生存空间,所以我们在让其可以弹性利用内存的同时,我们也要对其最大值进程限制。
控制组(cgroups) 是 Linux 内核的一种功能,允许系统管理员或用户对一组进程的资源使用情况进行限制、隔离和监控。它在容器技术、虚拟化、云计算以及多用户系统中非常有用,提供了对 CPU、内存、磁盘 I/O、网络带宽等系统资源的精细控制。
cgroups 的主要功能和用途:
1. 资源限制(Resource Limiting)
cgroups 允许你对进程组的资源使用进行限制,以防止某些进程消耗过多的系统资源,影响其他进程或系统的性能。例如:
- CPU 限制:可以为一组进程分配 CPU 资源的最大使用比例,从而避免某个进程独占 CPU。
- 内存限制:可以限制一组进程能够使用的内存量,防止某些进程消耗过多的内存导致系统崩溃。
- I/O 限制:可以限制进程访问磁盘或其他存储设备的速率,确保 I/O 操作的公平性,防止某些进程造成 I/O 瓶颈。
2. 资源隔离(Resource Isolation)
通过 cgroups,可以对不同的进程组进行资源隔离,确保一个进程组的资源使用不会影响到其他进程组。这对容器化环境特别有用:
- 每个容器可以被分配固定数量的资源,如 CPU 核心、内存、I/O 速率等,保证不同容器之间的资源隔离和公平使用。
- 资源隔离还可以用于虚拟机或多用户系统,确保不同的用户或服务不会相互干扰。
3. 资源计费与统计(Resource Accounting)
cgroups 可以实时跟踪和统计某个进程组使用的资源量,包括 CPU 时间、内存使用、网络带宽消耗、I/O 操作等。这对系统监控、调优以及计费系统非常有用。例如:
- 可以统计某个容器或进程组使用了多少 CPU 时间和内存。
- 在云服务中,可以用 cgroups 的资源统计功能来进行按量计费,记录每个虚拟机或容器的资源消耗。
4. 优先级控制(Priority Control)
cgroups 可以为不同的进程组设置资源使用的优先级,确保高优先级的任务能够优先获得资源。例如:
- 在 CPU 竞争时,可以优先让某些关键任务使用更多的 CPU 时间,确保关键任务的响应时间。
- 对于内存和 I/O 资源,也可以设置不同的权重,以保证系统中的重要进程获得优先资源。
优先级控制通过控制进程权重比例,CPU会根据权重比例分配时间片。不过我认为这个功能实际上是多余的,高并发应用会通过创建线程来抢占CPU资源,在我看来无需CPU通过分配时间片来进行控制,这样控制反而会造成不好的结果,比如说某些进程偶尔需要进行高并发,创建多个线程以提高性能,但由于控制组对其权重进行了限制,导致其虽然占用了更多的内存,但性能却没有明显提升。(个人思考,有别的想法也可以说出来一起讨论)
5. 进程管理和分组(Process Grouping and Management)
cgroups 提供了将多个进程分组管理的机制。每个进程组可以有不同的资源限制和策略,可以随时动态调整:
- 在容器化环境中,cgroups 管理着每个容器中的进程,确保容器内部的所有进程都受到相同的资源限制。
- cgroups 还允许系统管理员动态向某个进程组添加或移除进程,便于灵活管理系统资源。
docker容器相关命令
- 启动容器:
docker run -d -p <主机端口>:<容器端口> --name <容器名> <镜像>
#-d为后台启动,对于一些有自己的启动命令的服务。如nginx,我们可以直接-d,而对于一些没有启动命令的服务,我们还要给他一个持续运行的命令防止其在后台关闭进程
#-p为端口映射,如果外部需要访问服务,那么我们则需要将容器内端口映射到真实环境的端口
#--name 给运行的容器起名字 最后的镜像是运行容器的镜像
#我们也可以直接 docker run <服务名:版本> 这样会自动去远程仓库拉取镜像
- 停止容器:
docker stop <容器名/容器ID>
- 启动已停止的容器:
docker start <容器名/容器ID>
- 查看运行中的容器:
docker ps
- 查看所有容器(包括停止的):
docker ps -a
- 删除容器:
docker rm <容器名/容器ID>
- 进入正在运行的容器:
docker exec -it <容器名/容器ID> /bin/bash
- 查看容器日志:
docker logs <容器名/容器ID>
- 重启容器:
docker restart <容器名/容器ID>
联合文件系统
多个容器内部的服务所依赖的环境中,必定有重合的部分,不如java开发的服务,底层一定是要依赖于jdk,对于js开发的后端服务,必须依赖node环境,部分服务依赖于数据库如mysql来存储服务数据,docker容器环境直接调用操作系统底层系统调用,对于部分依赖于某个操作系统发行版的服务,其还需要centos,等等等等。
大量服务之间依赖相同环境,如果我们对其进行完全隔离,势必要在一个主机上安装多个相同依赖环境的服务,这是对于主机内存的极大浪费。然而如果我们想要对其进行复用时,多个服务对于同一个依赖的差异性又是一个问题,比如说我可能依赖mysql,但是我的mysql的配置可能和你不同。那么对于这种需求,操作系统采用了联合文件系统的方式对多个相同依赖环境进行复用,并完善差异化。
联合文件系统,将多个文件抽象成层的概念,其中我们所依赖的服务被一层一层的罗列起来,而容器内的服务则是要一层一层的访问这些服务,这些层全部都是只读层,无法进行更改。对于这些只读层的变更内容,则是放在第一层作为可写层,其中可写层的内容会对这些只读层的内容进行覆盖,最终的结构如下图:
这是对联合文件系统的抽象理解,其实本质上来说,就是多个容器对同一个环境进行使用,然后自己对差异的那日容进行维护,并对原环境覆盖,其中复用环境是相对简单的事情,最具意义的就是可写层的实现,可以对环境内容进行修改并覆盖,这是实现联合文件系统的关键。
镜像(image)
想要了解容器化,那就一定少不了镜像的概念。在了解完联合文件系统后,镜像的概念也变得非常简单了。对于容器内部所依赖的环境信息,以及对于环境信息修改的内容,以及容器内服务本身等关于服务容器化运行相关的信息记录,就是镜像,我们可以把镜像理解为容器的模板,对于一个服务的容器化运行,由docker按照镜像的模板来进行部署。
镜像相关命令
- 列出本地镜像:
docker images
- 拉取镜像:
docker pull <镜像名>:<标签>
- 构建镜像:
docker build -t <镜像名>:<标签> .
- 删除镜像:
docker rmi <镜像ID/镜像名>
- 查看镜像的历史记录:
docker history <镜像ID/镜像名>
数据卷挂载
对于容器内产生的需要持久化存储的数据,一旦删除容器后,数据也将不复存在,虽然容器并没有显示的将磁盘内数据进行删除,不过这片磁盘数据只被容器内部的文件系统所引用,一旦这个容器消失,文件系统也将不再,那么这个数据即使存储在磁盘中,相较于操作系统而言,也是没有这个数据,无法访问。
为了能够持久化存储数据,docker采用了一种数据卷的方式来实现。数据卷将容器需要持久化存储的数据不再保存在其内部文件中,而是保存在外部的主机文件中,而原容器内的文件则是保存了外部主机文件的一个引用,实现数据卷挂载,这样不仅实现了数据的持久化存储,对于一些配置的修改也可以直接修改主机文件,而不是进入容器内部,更加方便。
数据卷相关命令
- 列出所有数据卷
docker volume ls
- 创建数据卷
docker volume create <卷名>
- 查看数据卷的详细信息
docker volume inspect <卷名>
- 挂载数据卷到容器
docker run -d -v <卷名>:<容器内路径> <镜像>
- 删除数据卷
docker volume rm <卷名>
删除指定的数据卷。需要确保数据卷没有被任何正在运行的容器使用。
- 清理未使用的数据卷
docker volume prune
注意:这个操作是不可逆的,请谨慎使用。
- 挂载本地目录(绑定挂载)
docker run -d -v /宿主机路径:/容器内路径 <镜像>
- 列出特定数据卷
docker volume ls -f name=<关键词>
端口映射
同虚拟机一样,虚拟机在桥接模式下,其端口只能被当前主机访问。之前我们说过,docker和虚拟机这种桥接模式比较类似,所以在默认情况下,只能被当前主机访问。
不过既然是部署服务,自然不能局限于当前主机,所以我们可以通过一些命令,来讲端口映射到主机端口中,实现外部客户端访问此服务的目的。
端口映射相关命令
- 基本端口映射
docker run -d -p <宿主机端口>:<容器端口> <镜像>
-d
:后台运行容器(可选)。-p <宿主机端口>:<容器端口>
:将宿主机端口映射到容器的端口。
例子:
docker run -d -p 8080:80 nginx
这条命令会将宿主机的 8080
端口映射到容器中的 80
端口(Nginx 的默认端口),这样你可以通过 http://localhost:8080
访问容器中的 Nginx 服务。
- 映射多个端口
docker run -d -p <宿主机端口1>:<容器端口1> -p <宿主机端口2>:<容器端口2> <镜像>
可以使用 -p
多次来映射多个端口。
例子:
docker run -d -p 8080:80 -p 8443:443 nginx
这会将宿主机的 8080
端口映射到容器的 80
端口,将宿主机的 8443
端口映射到容器的 443
端口(Nginx 的 HTTPS 端口)。
- 随机映射宿主机端口
docker run -d -P <镜像>
使用大写的 -P
(或 --publish-all
),Docker 会将容器内暴露的所有端口随机映射到宿主机的端口上。
例子:
docker run -d -P nginx
这会随机将容器中暴露的 80
和 443
端口映射到宿主机的可用端口。
你可以使用 docker ps
来查看映射后的宿主机端口:
docker ps
- 指定绑定的宿主机 IP 地址
docker run -d -p <宿主机IP>:<宿主机端口>:<容器端口> <镜像>
默认情况下,Docker 将绑定到宿主机上的所有 IP 地址(0.0.0.0
),你也可以指定要绑定的 IP 地址。
例子:
docker run -d -p 127.0.0.1:8080:80 nginx
这会将宿主机的 127.0.0.1
(即 localhost
)的 8080
端口映射到容器的 80
端口。只有宿主机本地才能访问这个端口,而外部无法访问。
- 查看端口映射
docker port <容器ID/容器名>
显示容器内端口与宿主机端口的映射关系。
例子:
docker port my_nginx
这会列出名为 my_nginx
容器的所有端口映射信息。
- 修改正在运行的容器的端口映射
Docker 不支持在容器运行时直接修改端口映射。如果需要更改端口映射,需要停止容器并重新运行:
bashCopy codedocker stop <容器ID/容器名>
docker rm <容器ID/容器名>
docker run -d -p <新端口映射> <镜像>
其他命令
- 查看 Docker 信息:
docker info
- 查看 Docker 版本:
docker version
- 查看系统中的事件日志:
docker events
- 导出容器为镜像:
docker commit <容器ID> <镜像名>:<标签>
- 清理未使用的镜像、卷、网络和容器:
docker system prune
。
- 修改正在运行的容器的端口映射
Docker 不支持在容器运行时直接修改端口映射。如果需要更改端口映射,需要停止容器并重新运行:
bashCopy codedocker stop <容器ID/容器名>
docker rm <容器ID/容器名>
docker run -d -p <新端口映射> <镜像>
其他命令
- 查看 Docker 信息:
docker info
- 查看 Docker 版本:
docker version
- 查看系统中的事件日志:
docker events
- 导出容器为镜像:
docker commit <容器ID> <镜像名>:<标签>
- 清理未使用的镜像、卷、网络和容器:
docker system prune