三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Linux系统下Docker安装与核心概念实战指南

Linux系统下Docker安装与核心概念实战指南

1. 从“为什么需要Docker”开始聊起

如果你在Linux服务器上折腾过软件部署,大概率经历过这样的场景:为了部署一个应用,你需要先安装特定版本的Python,然后安装一堆依赖包,结果发现某个依赖包和系统里另一个服务的依赖版本冲突,导致整个环境崩掉。或者,你在一台Ubuntu 22.04上跑得好好的服务,迁移到CentOS 7上就各种报错,光是排查环境差异就耗掉大半天。这种“在我机器上能跑”的困境,是每个运维和开发者都头疼的问题。

Docker的出现,就是为了解决这个核心痛点。它不是一个轻量级的虚拟机,而是一个容器化技术。你可以把它理解为一个超级轻便、标准化的“软件集装箱”。这个集装箱里打包了你的应用代码、运行环境、系统工具、系统库和设置。无论这个集装箱被运到哪台支持Docker的Linux机器上(我们称之为“宿主机”),它都能以完全一致的方式运行起来。这彻底解决了环境不一致的问题,让开发、测试、生产环境的部署变得极其简单和可靠。

我最早接触Docker是在一个微服务项目里,当时十几个服务,每个服务的依赖环境都不同,手动部署简直是噩梦。自从用上Docker,所有服务的环境都被固化在镜像里,通过一个简单的docker-compose up命令就能拉起整个集群,部署时间从几小时缩短到几分钟。这种效率的提升是革命性的。所以,无论你是想学习一项热门技术,还是切实想提升自己的开发和部署效率,掌握Linux下的Docker都是非常值得投入时间的一步。

2. 安装前的准备:理清思路与避坑指南

在动手安装Docker之前,有几个关键点必须搞清楚,这能帮你避开后面90%的坑。很多人一上来就照着教程敲命令,结果遇到各种“virtualization support not detected”或者权限问题,其实根源都在准备阶段没做好。

首先,明确你的Linux发行版和版本。Docker对主流发行版的支持都很好,但安装方式略有不同。最常见的是基于Debian的(如Ubuntu、Debian)和基于RPM的(如CentOS、RHEL、Fedora)。你需要用cat /etc/os-release命令确认一下。这篇文章的实操部分会以Ubuntu 22.04 LTSCentOS 7这两个最典型的系统为例进行讲解,其他发行版可以举一反三。

其次,理解Docker的架构和核心概念。Docker采用的是客户端-服务器(C/S)架构。我们平时在命令行里敲的docker命令,其实是Docker客户端(Client)。它通过REST API与一个常驻后台进程通信,这个后台进程就是Docker守护进程(Daemon),它负责创建、运行和管理容器。我们安装Docker,主要就是安装这个守护进程、客户端以及相关的工具链。

最后,也是最重要的,检查系统环境。这里有两个大坑:

  1. 内核版本:Docker需要较新的Linux内核来支持其底层特性(如cgroups、namespaces)。对于生产环境,建议内核版本在3.10以上。用uname -r查看。
  2. 存储驱动:Docker镜像和容器数据需要存储驱动来管理。对于较新的发行版,默认的overlay2驱动是首选,性能最好。但有些旧系统或云主机可能用了devicemapper,这可能会带来性能问题。安装后需要确认。

注意:网上很多教程会提到要“开启虚拟化支持”,这通常是针对在Windows或macOS上安装Docker Desktop时的要求。在纯Linux环境下安装Docker Engine(也就是我们常说的Docker),并不需要CPU的虚拟化技术(如Intel VT-x/AMD-V)。Docker容器直接共享宿主机内核,利用的是Linux内核的cgroup和namespace等特性来实现隔离,这与虚拟机(VM)的硬件虚拟化是完全不同的技术路线。所以,如果你在Linux主机上遇到类似“virtualization support not detected”的错误提示,那很可能是你错误地尝试安装Docker Desktop for Linux,或者混淆了概念。在Linux上,我们安装的是原生的Docker Engine。

3. Ubuntu 22.04 系统下的Docker安装详解

Ubuntu是个人学习和开发环境中使用最广泛的Linux发行版,其软件包管理工具apt非常方便。下面我们走一遍最稳妥的安装流程,我会解释每一步的作用。

3.1 卸载旧版本与配置仓库

第一步永远是清理战场,避免旧版本残留导致冲突。即使你是新系统,执行一下也无妨。

sudo apt-get remove docker docker-engine docker.io containerd runc

这条命令移除了可能存在的旧版Docker包。containerdrunc是Docker依赖的底层容器运行时,我们后续会安装更新的版本。

接下来,我们需要让apt知道从哪里获取Docker官方软件包。Docker官方维护了一个APT仓库,比Ubuntu自带源中的版本更新、更稳定。

# 更新apt包索引并安装一些工具,这些工具允许apt通过HTTPS使用仓库 sudo apt-get update sudo apt-get install \ ca-certificates \ curl \ gnupg \ lsb-release # 添加Docker官方的GPG密钥。GPG密钥用于验证软件包的签名,确保你下载的包是官方发布的,没有被篡改。 sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg # 设置稳定版仓库。这里将仓库地址写入/etc/apt/sources.list.d/docker.list文件。 echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

这里有个细节:$(lsb_release -cs)会自动获取你的Ubuntu代号,比如jammy(对应22.04)。这确保了仓库地址与你的系统版本匹配。

3.2 安装Docker Engine

仓库配置好后,安装就很简单了。

# 再次更新apt包索引,这次它会读取我们刚添加的Docker仓库信息。 sudo apt-get update # 安装Docker Engine、命令行工具cli、containerd以及docker-compose-plugin。 # 注意包名是`docker-ce`(社区版),`docker-ee`是企业版。 sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

执行这个命令后,apt会自动处理所有依赖。安装完成后,Docker守护进程会自动启动。

3.3 验证安装与权限配置

安装是否成功,跑一个最经典的测试镜像就知道了。

sudo docker run hello-world

如果看到“Hello from Docker!”等欢迎信息,说明Docker已经成功安装并可以拉取和运行容器了。

但每次命令都要加sudo很麻烦,而且有安全风险。通常我们会将当前用户加入docker用户组,这样就能直接执行docker命令。

# 创建docker用户组(通常安装时已自动创建) sudo groupadd docker # 将当前用户加入docker组 sudo usermod -aG docker $USER

关键一步:退出当前终端并重新登录,或者新开一个终端,用户组的更改才会生效。之后,你就可以不用sudo直接运行docker ps等命令了。

验证一下:

docker run --rm ubuntu:22.04 /bin/echo 'Hello, Docker!'

能成功输出信息,说明权限配置正确。

4. CentOS 7 系统下的Docker安装与差异点处理

CentOS 7(以及RHEL 7)在企业服务器中非常常见,但它的内核版本相对较老,且使用yum作为包管理器,安装过程与Ubuntu有显著区别。

4.1 清理环境与添加仓库

CentOS的Extras仓库提供了一个旧版本的docker包(实际上是docker.io),但版本非常老旧,我们一定要先卸载它,然后使用Docker官方仓库。

# 卸载旧版本 sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine # 安装yum工具包,它提供了`yum-config-manager`工具,用于管理仓库。 sudo yum install -y yum-utils # 添加Docker官方仓库 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

4.2 安装Docker Engine及可能的内核升级

CentOS 7默认内核是3.10,虽然满足Docker的最低要求,但某些新特性可能不支持。如果条件允许,建议升级到更新的内核(如ELRepo仓库的长期支持版内核)。这里我们先按默认内核安装。

# 安装Docker Engine sudo yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

如果安装过程中提示container-selinux版本冲突,这是因为Docker依赖的container-selinux版本高于CentOS默认仓库提供的。你需要从CentOS Extras仓库安装一个特定版本,或者先安装centos-release-extras包。一个常见的解决命令是:

sudo yum install -y https://download.docker.com/linux/centos/7/x86_64/stable/Packages/containerd.io-<version>.rpm # 然后再安装docker-ce

具体版本号需要去Docker仓库查看。这是CentOS 7安装Docker时最常见的坑。

4.3 启动服务与配置防火墙

CentOS使用systemd管理服务,且默认开启了防火墙firewalld,需要额外配置。

# 启动Docker守护进程 sudo systemctl start docker # 设置开机自启 sudo systemctl enable docker # 验证安装 sudo docker run hello-world

防火墙配置:Docker守护进程默认会创建一个docker0的网桥,容器通过它进行网络通信。为了让容器能与外界(或宿主机)正常通信,需要在防火墙中放行相关流量。

# 永久放行Docker服务使用的端口范围(默认是2375/2376,但通常我们不用) # 更常见的做法是,信任docker0网桥的流量 sudo firewall-cmd --permanent --zone=trusted --add-interface=docker0 sudo firewall-cmd --reload

如果你在容器内运行了Web服务(如映射了80端口),还需要在防火墙中放行对应的宿主机端口:

sudo firewall-cmd --permanent --add-port=80/tcp sudo firewall-cmd --reload

同样,记得将用户加入docker组以避免sudo

sudo groupadd docker sudo usermod -aG docker $USER # 重新登录后生效

5. Docker核心概念与基础命令实战

安装好了,我们来真正用起来。要熟练使用Docker,必须理解三个核心概念:镜像(Image)容器(Container)仓库(Registry)。它们的关系就像面向对象编程中的对象

  • 镜像(Image):一个只读的模板,里面包含了运行应用所需的代码、库、环境变量和配置文件。它相当于一个“类”的定义。例如ubuntu:22.04,nginx:latest,python:3.9-slim
  • 容器(Container):镜像的一个运行实例。你可以创建、启动、停止、删除容器。容器与容器之间是相互隔离的。它相当于根据“类”创建出来的一个“对象”。
  • 仓库(Registry):集中存放镜像的地方。最大的公共仓库是 Docker Hub ,你可以从这里拉取镜像,也可以推送自己的镜像上去。私有的仓库可以自己搭建,比如用Harbor。

下面是一组你最需要掌握的基础命令,我附上了实际使用场景和解释。

5.1 镜像相关操作

# 从仓库拉取镜像。如果不指定标签,默认拉取 `latest`。 # 建议生产环境总是指定明确的版本标签,避免因`latest`更新导致意外。 docker pull nginx:1.23-alpine # 列出本地已下载的镜像 docker images # 或者使用更强大的新命令 `docker image ls` docker image ls # 搜索Docker Hub上的镜像(通常直接去网站搜更直观) docker search mysql # 删除本地镜像。如果镜像有对应的容器(即使已停止),需要先删除容器才能删除镜像。 # 使用 `-f` 可以强制删除,但慎用。 docker rmi nginx:1.23-alpine # 删除所有未被容器使用的镜像(悬空镜像) docker image prune # 删除所有未被使用的镜像(包括未被任何容器引用的镜像) docker image prune -a

5.2 容器生命周期管理

这是最核心的部分,容器就像一个轻量级的进程。

# 运行一个容器 # `-d`: 后台运行(detached mode) # `--name mynginx`: 给容器起个名字,否则Docker会随机分配一个 # `-p 8080:80`: 端口映射,将宿主机的8080端口映射到容器的80端口 # `nginx:alpine`: 使用的镜像 docker run -d --name mynginx -p 8080:80 nginx:alpine # 此时,在浏览器访问 http://你的服务器IP:8080,就能看到Nginx欢迎页。 # 列出正在运行的容器 docker ps # 列出所有容器(包括已停止的) docker ps -a # 停止一个运行中的容器 docker stop mynginx # 启动一个已停止的容器 docker start mynginx # 重启容器 docker restart mynginx # 进入一个正在运行的容器的命令行终端 # `-it` 是 `-i` (交互式) 和 `-t` (分配一个伪终端) 的组合,让你可以像SSH一样操作容器。 docker exec -it mynginx /bin/sh # 对于基于Alpine的镜像,shell通常是`/bin/sh`;对于Ubuntu等,是`/bin/bash`。 # 查看容器的日志输出,排查问题非常有用 docker logs mynginx # 实时查看日志(类似 `tail -f`) docker logs -f mynginx # 删除一个已停止的容器 docker rm mynginx # 强制删除一个运行中的容器(不推荐,应先停止) docker rm -f mynginx # 删除所有已停止的容器(清理空间) docker container prune

5.3 容器与宿主机的数据交互

容器默认是“无状态”的,其内部文件系统的改动在容器删除后会丢失。为了持久化数据(如数据库文件、配置文件、日志),我们需要使用卷(Volume)绑定挂载(Bind Mount)

卷(Volume):由Docker管理,存储在宿主机文件系统的某个区域(通常是/var/lib/docker/volumes/),与容器的生命周期解耦。这是持久化数据的推荐方式。

# 创建一个名为 `my-vol` 的卷 docker volume create my-vol # 运行容器并使用这个卷,将容器内的 `/app/data` 目录挂载到卷上 docker run -d --name myapp -v my-vol:/app/data myapp-image # 即使 `myapp` 容器被删除,`my-vol` 卷及其数据依然存在,可以被其他容器挂载。

绑定挂载(Bind Mount):将宿主机上的一个特定目录或文件直接挂载到容器中。常用于开发环境,方便在宿主机上修改代码,容器内实时生效。

# 将宿主机的 `/home/user/project` 目录挂载到容器的 `/app` 目录 docker run -d --name dev-app -v /home/user/project:/app dev-app-image # 现在你在宿主机上修改代码,容器内的 `/app` 下文件也会同步变化。

6. 使用Dockerfile定制专属镜像

从公共镜像仓库拉取镜像很方便,但更多时候我们需要构建自己的镜像,比如部署自己的Python应用、Java应用。这就需要编写Dockerfile。Dockerfile是一个文本文件,包含了一条条构建镜像所需的指令。

下面我们以一个简单的Python Flask应用为例,手把手构建一个镜像。

项目结构

myflaskapp/ ├── app.py ├── requirements.txt └── Dockerfile

app.py(一个最简单的Flask应用):

from flask import Flask app = Flask(__name__) @app.route('/') def hello(): return 'Hello, Docker!' if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)

requirements.txt

Flask==2.3.2

Dockerfile

# 第一阶段:使用官方Python轻量级镜像作为构建环境 FROM python:3.9-slim as builder # 设置工作目录,后续命令都在这个目录下执行 WORKDIR /app # 将依赖文件复制到工作目录 COPY requirements.txt . # 安装依赖。使用清华PyPI镜像加速,并将依赖安装到 /usr/local RUN pip install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt # 第二阶段:使用更小的运行时镜像,只复制必要的文件 FROM python:3.9-slim # 设置工作目录 WORKDIR /app # 从构建阶段复制已安装的Python包 COPY --from=builder /usr/local /usr/local # 复制应用代码 COPY . . # 声明容器运行时监听的端口 EXPOSE 5000 # 定义容器启动时执行的命令 CMD ["python", "app.py"]

逐行解释

  • FROM: 指定基础镜像。我们使用了两阶段构建,第一阶段builder用于安装依赖,第二阶段使用同一个基础镜像但只复制安装结果,可以显著减小最终镜像体积。
  • WORKDIR: 设置工作目录,相当于cd到这个目录。
  • COPY: 将宿主机文件复制到镜像内。
  • RUN: 在构建镜像时执行的命令,这里用来安装依赖。我们使用了国内镜像源加速下载。
  • EXPOSE: 声明容器打算使用的端口,这是一个文档性质的说明,实际端口映射需要在docker run时用-p指定。
  • CMD: 指定容器启动时默认执行的命令。一个Dockerfile中只能有一条CMD指令。

构建镜像: 在myflaskapp目录下执行:

# `-t` 给镜像打标签,格式为 `名称:标签`,标签默认为 `latest` # `.` 表示Dockerfile所在的当前目录为构建上下文 docker build -t my-flask-app:1.0 .

运行容器

# 将宿主机的5000端口映射到容器的5000端口 docker run -d --name myapp -p 5000:5000 my-flask-app:1.0

访问http://localhost:5000,就能看到“Hello, Docker!”了。

7. 使用Docker Compose编排多容器应用

现实中的应用很少是单个容器,通常由多个服务组成,比如一个Web应用需要搭配数据库、缓存等。手动用docker run启动每一个容器并配置网络链接非常繁琐。Docker Compose就是用来定义和运行多容器Docker应用的工具。它使用一个YAML文件(默认docker-compose.yml)来配置所有服务。

假设我们有一个WordPress博客,需要WordPress容器和MySQL数据库容器。

创建一个docker-compose.yml文件:

version: '3.8' # 指定Compose文件格式版本 services: # 数据库服务 db: image: mysql:8.0 # 容器重启策略:除非手动停止,否则总是重启 restart: always environment: # 设置MySQL的root密码和环境变量,这是必须的 MYSQL_ROOT_PASSWORD: somewordpress MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress # 命名卷,用于持久化数据库数据 volumes: - db_data:/var/lib/mysql # 自定义网络,让服务间可以通过服务名通信 networks: - wp-network # WordPress服务 wordpress: depends_on: - db # 声明依赖,确保db服务先启动 image: wordpress:latest restart: always ports: - "8080:80" # 将宿主机的8080端口映射到WordPress容器的80端口 environment: # 告诉WordPress如何连接数据库,这里`db`就是上面定义的服务名 WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: wordpress WORDPRESS_DB_NAME: wordpress volumes: # 绑定挂载,将当前目录下的`wp-content`目录挂载,方便主题/插件管理 - ./wp-content:/var/www/html/wp-content networks: - wp-network # 定义卷,Docker Compose会自动创建和管理它 volumes: db_data: # 定义网络,Docker Compose会自动创建一个名为`项目名_wp-network`的网络 networks: wp-network: driver: bridge

运行与管理: 在包含docker-compose.yml的目录下,执行以下命令:

# 启动所有服务(后台运行) docker-compose up -d # 查看服务状态 docker-compose ps # 查看所有服务的日志 docker-compose logs # 查看特定服务(如wordpress)的日志 docker-compose logs wordpress # 停止所有服务 docker-compose stop # 停止并删除所有容器、网络(但保留卷) docker-compose down # 停止并删除所有容器、网络、卷(数据也会被删除!) docker-compose down -v

Docker Compose极大地简化了多服务应用的部署和管理,是开发测试环境的利器。对于更复杂的生产环境集群编排,则需要用到Kubernetes (K8s)Docker Swarm

8. 生产环境部署的进阶考量与优化

将Docker用于个人学习或开发很简单,但一旦要上生产环境,就需要考虑更多因素:安全性、性能、日志、监控、镜像仓库等。

1. 镜像优化与安全扫描

  • 使用多阶段构建:如上文Flask例子所示,可以大幅减小最终镜像体积,减少攻击面。
  • 使用特定标签:不要使用latest标签,而应使用明确的版本号,如python:3.9.16-slim-bullseye
  • 使用非root用户运行容器:在Dockerfile中创建并使用非root用户。
    RUN groupadd -r appuser && useradd -r -g appuser appuser USER appuser
  • 定期扫描镜像漏洞:可以使用docker scan命令(集成Snyk)或第三方工具(如Trivy、Clair)扫描镜像中的已知安全漏洞。

2. 日志管理容器默认将日志输出到stdoutstderr,Docker守护进程会捕获并存储。在生产环境中,需要将日志集中收集和管理。

  • 使用日志驱动:可以配置Docker守护进程将日志发送到json-file(默认)、syslogjournaldfluentd等。
  • 使用Docker Compose的logging配置:可以限制日志文件大小,防止磁盘被撑满。
    services: myapp: image: myapp logging: driver: "json-file" options: max-size: "10m" max-file: "3"

3. 资源限制防止单个容器耗尽宿主机资源。

# 运行容器时限制CPU和内存 docker run -d --name myapp \ --cpus="1.5" \ # 限制使用1.5个CPU核心 --memory="512m" \ # 限制内存为512MB --memory-swap="1g" \ # 限制内存+交换分区总量为1GB myapp-image

4. 使用私有镜像仓库生产环境不应直接从Docker Hub拉取镜像。应该搭建私有仓库(如Harbor、Nexus)或使用云服务商提供的仓库(如ACR、ECR、GCR)。

  • 登录私有仓库docker login myregistry.example.com
  • 给镜像打上私有仓库标签docker tag myapp:1.0 myregistry.example.com/myapp:1.0
  • 推送镜像docker push myregistry.example.com/myapp:1.0
  • 拉取镜像docker pull myregistry.example.com/myapp:1.0

5. 服务发现与网络对于多主机集群,Docker原生的bridge网络不够用。需要结合Overlay网络(Swarm模式)或直接使用Kubernetes的CNI(容器网络接口)插件来管理跨主机的容器网络。

从单机安装到多容器编排,再到生产环境的考量,Docker的学习路径是循序渐进的。我个人的经验是,先在本机或虚拟机里把单容器和Docker Compose玩熟练,理解镜像、容器、数据卷、网络这些核心概念。当你的应用变得复杂,需要高可用、自动伸缩时,再去探索Kubernetes的世界。Docker是这一切的基石,扎实的基础会让你后续的学习轻松很多。在实际操作中,最常遇到的坑往往是权限问题(Got permission denied)、端口冲突、镜像拉取慢(记得配置国内镜像加速器)以及磁盘空间不足(记得定期prune)。多动手,多查日志,解决问题的过程本身就是最好的学习。

← 返回列表