Administrator
发布于 2025-10-09 / 2 阅读
0

告别依赖地狱,拥抱云原生:Docker容器化简明入门指南

文章来源: https://idcflare.com/t/topic/18843/1

你是否还在为服务器上恼人的环境配置而头疼?你是否还在为“在我电脑上明明是好的”而抓狂?你是否还在不同项目冲突的依赖版本之间苦苦挣扎?

Docker是什么:迈向云原生的第一级阶梯

Docker不只是一个简单的工具,它是赋予项目灵活自由的灵丹妙药,是救运维于水火的得力帮手。在网络上你会看到各种不一样的声音,他们说Docker设计笨拙、屎山、反人类、存在安全问题……好吧,我承认这些问题大多是事实(其实我也在骂),但无可辩驳的是,Docker是所有项目拥抱云原生,迈向现代化,实现心脑血管友好运维方案的黄金准则。

为什么我要使用Docker:容器化的核心优势

Docker,英文本义为"码头工人"。这个名字恰如其分。Docker正在做的,就是将你的每个项目,分别装进设施完善、互不影响的"集装箱"里。这个集装箱的名字,就叫容器。从今往后,无论外界发生怎样的变化,容器内的项目,总是能够保持绝对的稳定性和一致性。

容器中需要暴露的端口,那就映射到宿主机网卡;容器中需要持久化储存的文件,那就映射到宿主机目录。Docker容器的哲学,总而言之就是极简和专注,就是高内聚、低耦合,就是"一次构建,处处运行"。

Docker的核心:镜像与容器

纵观Docker,几乎完全围绕着镜像与容器的概念展开。

镜像是特定项目的模板。它包含了运行某个项目所需的完整环境,包括操作系统、依赖库、配置以及应用程序本身。镜像的任务是定义“软件应当在什么环境下,以什么方式运行”,因此它是只读的、不可变的。
容器则是镜像的实例。启动一个容器时,Docker便会基于指定的镜像,创建一个隔离、可写的运行环境。镜像是静态的,而容器具有生命周期。它会被创建、启动、停止,最终销毁。

如此设计赋予了Docker多种优势:镜像具有高度的不可变性(无论在哪里运行,环境都是完全一致的)和可复用性(同一个镜像可以启动无数个互不影响的实例),容器又同时具备可写性(程序可以按照正常逻辑处理数据,但不会影响镜像)。

终结“依赖地狱”,拥抱环境隔离

在传统服务器上,我们经常遇到这样的噩梦场景:项目A依赖于 glibc 2.27 ,而项目B又利用了 glibc 2.42 特性,无论安装哪个版本,总有项目水土不服;在Nodejs项目中,这种跨版本的“不兼容”情况更是家常便饭。服务器上每新增一个项目,都可能引入新的依赖冲突问题。久而久之,服务器的运维工作将举步维艰。

Docker的存在,为彻底终结依赖地狱,提供了一个极致优雅的解决方案。

如我们之前的文章所说,容器化可以看做是一种广义上的虚拟化方案。Docker在宿主机的内核中,为每个容器分配独立的命名空间和控制组,创造出一个相对“完整”的Linux环境。每个容器中运行的Linux发行版及其依赖,相互独立,绝不冲突。你可以在一个容器里运行 Nodejs14 远古项目,在另一个容器里运行 nodejs20 新项目,因为“集装箱”的隔离性,它们可以友好地共存在同一台宿主机上。

images (1)

168×299 7.78 KB

快速备份迁移,提升运维效率

传统服务器的迁移与备份,是一项高风险、费事且费力的复杂工作。迁移一个简单的PHP-MySQL-WordPress博客,其必须经历网站下线->网站目录打包->MySQL数据库导出->新服务器Nginx, PHP, MySQL安装配置->文件传输和解压->数据库恢复->启动并调试的工作流程,任何一步出错,都有可能导致迁移后的网站出现(甚至不一定能即时发现的)严重问题。

Docker容器化则将最复杂、重复、耗时、容易出错的环境配置工作彻底解放,同一个项目,在进行正确的Docker容器化后,它的迁移步骤可以简化为:docker-compose down停止项目->打包项目目录->文件传输和解压->docker-compose up -d。整个过程清晰、高效且极少出错,运维人员只需要关注最重要的项目配置和持久化数据,而它们正整整齐齐地归档在同一个目录下。

高度的兼容性,开发测试部署全兼容

“在我电脑上能跑”,在Docker的世界里不再是笑话。Docker几乎打包了软件所需要的一切,只要在我这能跑,那么在你那也一定能跑。无论是Ubuntu,Debian,还在CentOS,Fedora,只要它们正确地安装了Docker,那么你的服务就应当以预期的方式正常运行。宿主机缺库?关我容器环境什么事!

docker-is-born-v0-rq1glermrwu91

docker-is-born-v0-rq1glermrwu91422×585 42 KB

没有任何技术是完美的:拥抱Docker,你不得不知道的重要信息

额外的学习成本:端口访问与数据持久化

Docker的容器,是镜像+数据的集合。镜像是稳定的、无状态的,每一次销毁镜像并重建,它就会将除了-v指定的持久化目录以外的目录,重置回最初的状态。这意味着容器内的所有变化都会丢失,除非它们被显式地持久化到宿主机上。这就是为什么正确理解和使用数据卷(Volume)或绑定挂载(Bind Mount)如此重要。

Warning

有些人依赖于容器停止并启动而非销毁重建,使用docker commit保存容器状态,这是非常不推荐的反模式做法,它破坏了镜像无状态的最佳实践,使得镜像臃肿且难以管理。

一个完整的应用(如WordPress)需要PHP、数据库、Web服务器等多个容器协同工作,它们通常需要访问其他容器的端口。这需要正确学习Docker的网络模式,并进行正确的配置。

例如,一个标准的WordPress+MySQL Docker-compose可以设计为如下:

version: '3.8'

# 定义一个虚拟网络,让容器之间可以靠容器名/项目名互相访问
networks:
  mywpsite:
    name: mywpsite

services:
  web:
    image: wordpress:php8.3-apache
    container_name: web
    networks:
      - mywpsite
    volumes:        # 持久化网站数据和PHP配置文件
      - ./website:/var/www/html
      - ./conf/php/php.ini:/usr/local/etc/php/php.ini
    environment:
      WORDPRESS_DB_HOST: mysql      # 使用 mysql 主机名访问 mysql 项目对应的容器
      WORDPRESS_DB_USER: mywpsite
      WORDPRESS_DB_PASSWORD: mysqlpassword
      WORDPRESS_DB_NAME: mywpsite
    ports:
      - "127.0.0.1:30080:80"
    user: "1000:1000"

  mysql:
    image: mysql:8.0.37-debian
    container_name: mysql
    networks:
      - mywpsite
    environment:
      MYSQL_DATABASE: mywpsite
      MYSQL_USER: mywpsite
      MYSQL_PASSWORD: mysqlpassword
      MYSQL_ROOT_PASSWORD: mysqlpassword
      MYSQL_INITDB_SKIP_TZINFO: "1"
    volumes:
      - ./mysql:/var/lib/mysql      # 持久化MySQL数据库文件
    ports:
      - "127.0.0.1:33306:3306"
    command: --sql-mode=""

严重的安全隐患:被绕过的防火墙

正如前文所言,Docker始于一个目前看起来十分草率的、缺乏前瞻性的设计。但为了保证绝对的向后兼容,一些存在着严重问题乃至安全风险的设计,依旧保留至今。而其中最突出的,就是Docker对于iptables的反直觉控制。

我们总是认为,一个程序监听一个端口时,它不会,也绝不应该去告诉iptables放行它。这是一般程序设计的通用规则,我们很容易将其代入Docker容器中,认为当我们启动容器并使用-p映射一个端口时,这个端口应当依旧由iptables的INPUT链全权负责,遵守用户设定的 DROP 等规则。

然而事实上,Docker在iptables里使用了自己的DOCKER链处理相应端口的代理行为,直接绕过INPUT链管理端口,这导致了尽管你的容器端口已经裸奔在了公网,iptables INPUT链,UFW等防火墙工具,都完全不会有任何的迹象。

643608a042cb3ab79c685208be5bf71c71741dba1c33fd03624bbc9ea44ebde5

643608a042cb3ab79c685208be5bf71c71741dba1c33fd03624bbc9ea44ebde5792×951 75.7 KB

因此,最标准的实践就是,如果你不想让Docker容器的端口暴露在公网,那么请显式地让它监听127.0.0.1。

资源与迁移的权衡

一个优秀的Docker项目,应当在同一个项目目录下完成配置文件和持久化数据的管理,使得运维在迁移时能够做到“提桶跑路,拎包入住”。然而,一些项目在创建之初就不是为了云原生适配的,它们在容器化时往往面临尴尬的境地,问题随之变得复杂了起来。

假设多个项目都需要MySQL,那么你将面临以下选择:

如果选择为每个项目启动一个MySQL实例,在自己的项目目录下持久化MySQL数据卷,那么每个MySQL容器实例都会独立占用一份内存(通常可以高达200MB以上)。这对于一般的小内存服务器而言,几乎是不可接受的,3-5个项目就足以拖垮整个服务器

但如果使用一个高性能MySQL实例集中管理所有数据库,那么问题又来了——复用配置进行多开是不可能的,不同项目会在同一个实例访问同名的数据库;MySQL也没有按数据库隔离本地文件的功能,迁移时需要额外经历数据库导出导入的步骤。

当然,这个问题并没有绝对完美的答案,只是需要用户在“极致隔离”与“资源效率”之间做出抉择。Docker-compose在此稍显局限,想要找到一个更加接近“银弹”的方案,还是需要更高级的容器化编排工具——Kubernetes。

一个更加讨巧的方案是,在个人博客、文档项目等读多写少的情形下,使用SQLite替代MySQL,是实现极致兼容性、读性能和便利性的绝佳选择。它将单个数据库存储在独立的文件中,完美契合容器化“打包带走”的哲学。

怎么做:开始一个Docker项目

第一步,安装Docker

Note

如果你使用的是1panel面板,那么Docker与Docker-compose应当在面板部署时就自动安装妥当。我强烈建议新手优先使用1panel入门Docker容器化。

Warning

以下教程适用于Debian系统及apt包管理器。如果你需要在其他系统下安装Docker,请自行替换相应的命令与包名。
仍然强烈推荐使用1panel一步到位的自动化安装方式。

卸载旧版本(如果之前安装过Docker)

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

设置Docker apt仓库

sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg

# 添加 Docker 官方 GPG 密钥
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# 设置 Docker 的 apt 源
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

安装Docker全家桶

# 更新 apt 包索引
sudo apt-get update

# 安装 Docker Engine, CLI, Containerd, 以及 Compose 插件
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

如果你正在以非root用户操作,那么建议将当前用户添加到Docker组,以便日常使用。

sudo usermod -aG docker $USER
newgrp docker

使用Docker-CLI快速启动单一项目:以Uptime-kuma为例

为项目创建一个目录,在目录下执行以下命令:

docker run \
  -d \
  --restart=always \
  -p 127.0.0.1:3001:3001 \
  -v ./uptime-kuma:/app/data \
  --name uptime-kuma \
  louislam/uptime-kuma:1

这个命令直观展现了镜像与容器的关系:

  • louislam/uptime-kuma:1镜像(包括版本号),它包含了uptime-kuma应用的完整运行环境

  • Docker会基于这个镜像,创建一个名为uptime-kuma容器实例

  • -v ./uptime-kuma:/app/data 将容器内的/app/data挂载到宿主机的./uptime-kuma目录,即使容器重建,数据也不会丢失。

访问127.0.0.1:3001(注意,这里仅监听127.0.0.1。如果需要外部访问,推荐使用Nginx进行反向代理),你就可以看到uptime-kuma顺利运行起来了。

*参数解释

docker run \
  -d \                      # 后台运行容器
  --restart=always \        # 容器退出时总是自动重启
  -p 127.0.0.1:3001:3001 \  # 将容器的3001端口映射到宿主机的127.0.0.1:3001
  -v ./uptime-kuma:/app/data \ # 将持久化数据挂载到当前目录下的 uptime-kuma 子目录
  --name uptime-kuma \      # 为容器命名
  louislam/uptime-kuma:1      # 指定要使用的镜像及其版本

使用Docker-compose编排大型项目

一个项目通常需要多个组件和服务提供支持,并非单一容器、单一进程所能解决。Docker-compose的存在,就是为了帮助用户系统地管理整个项目的所有组件。

以下是我的网站项目的Docker-compose,包含了网站系统的全部服务(数据已做脱敏处理)。

version: '3.8'

networks:  # 自定义网络
  webapp:
    name: webapp

services:
  web:
    image: wordpress:php8.3-apache   # 基础镜像
    container_name: web    # 指定启动时使用的容器名
    networks:
      - webapp  # 使用刚才创建的网络
    volumes:     # 映射多个目录
      - ./website:/var/www/html
      - ./conf/php/php.ini:/usr/local/etc/php/php.ini
    environment:   # 配置环境变量
      ALLOW_EMPTY_PASSWORD: yes
    ports:   # 映射端口
      - "127.0.0.1:30080:80"
    user: "1000:1000"   # 将主机用户组映射到容器

  mysql:
    image: mysql:8.0.37-debian
    container_name: mysql
    networks:
      - webapp
    environment:
      MYSQL_DATABASE: myapp
      MYSQL_USER: dbuser
      MYSQL_PASSWORD: SecurePass123!
      MYSQL_ROOT_PASSWORD: RootPass456!
      MYSQL_INITDB_SKIP_TZINFO: "1"
    volumes:
      - ./mysql:/var/lib/mysql
    ports:
      - "127.0.0.1:33306:3306"
    command: --sql-mode=""   # 配置主进程启动参数

  redis:
    image: redis:7.4-rc2-alpine3.20
    container_name: redis
    networks:
      - webapp
    volumes:
      - ./redis:/data
    mem_limit: 512m     # 限制内存使用为 512MB
    memswap_limit: 1g   # 限制内存和交换空间总和为 1GB

Warning

一个Docker-compose配置应当包含且仅包含一个完整的项目,不推荐在一份配置中包含多个项目。

WordPress-SQLite实现极致轻量化个人博客

个人博客往往是典型的读多写少场景。对于容器化场景而言,使用SQLite不仅能够极大降低资源消耗,还能彻底规避MySQL等数据库项目的数据耦合问题,实现极致的轻量、解耦和迁移效率。

以下是可供参考的实践案例:

services:
  mywpblog:
    image: soulteary/sqlite-wordpress:6.5.3
    restart: always
    ports:
      - 127.0.0.1:44420:80
    volumes:
      - ./wordpress:/var/www/html

结语

Docker不仅仅是一项技术,更是一种思维方式的转变。它通过一致的容器环境,统一了开发、调试、生产环境,抹平了不同宿主机环境的差异,免去了繁重、重复且不必要的环境配置工作。虽然其存在一定的学习曲线,但它所带来的运维效率提升是飞跃性的。学习容器化,对于大多数场景而言,都是完全值得付出的投资。