跳转至

Docker

知识的反刍是适当且必要的 🐮

本篇内容完全借鉴于:

这篇笔记重点在于描述docker的 提出背景设计原理常见指令的cheat sheet

提出背景

环境配置的痛点

软件开发最大的麻烦事之一, 就是环境配置。用户计算机的环境都不相同, 怎么知道自家的软件, 能在别人的机器跑起来?

用户必须保证两件事:操作系统的设置, 各种库和组件的安装。只有它们都正确, 软件才能运行。举例来说, 安装一个 Python 应用, 计算机必须有 Python 引擎, 还必须有各种依赖, 可能还要配置环境变量。

如果某些老旧的模块与当前环境不兼容, 那就麻烦了。开发者常常会说:"它在我的机器可以跑了"(It works on my machine), 言下之意就是, 其他机器很可能跑不了。

环境配置很麻烦, 换一台机器, 就要重来一次。很多人想:我们能不能从根本上解决问题, 软件可以带环境安装?也就是说, 安装的时候, 把原始环境一模一样地复制过来。

带环境配置软件的类型

基于上述的想法, 我们有以下两种设计思路:

(1) 使用虚拟机 (virtual machine)

  1. 使用 virtual machine:
    1. 可以在一种操作系统里面运行另一种操作系统, 如: 在 Windows 系统里面运行 Linux 系统
    2. 应用程序对此毫无感知, virtual machine 对上层应用完全透明
  2. 弊端:
    1. 资源占用量太高: 每次配置都是“操作系统level”的
    2. 启动速度慢: 每次启动都是“启动OS”, 因此很耗时

(2) 使用容器 (container):

由于虚拟机存在这些缺点, Linux 发展出了另一种虚拟化技术: Linux 容器(Linux Containers, 缩写为 LXC)

Linux 容器不是模拟一个完整的操作系统, 而是对进程进行隔离。或者说, 在正常进程的外面套了一个保护层

对于容器里面的进程来说, 它接触到的各种资源都是虚拟的, 从而实现与底层系统的隔离

由于容器是进程级别的, 相比虚拟机有很多优势:

  1. 启动速度快:
    1. 进程-level vs. OS-level
    2. 容器里面的应用, 本质上就是底层系统的一个进程, 而不是虚拟机内部的进程。所以, 启动容器相当于启动本机的一个进程, 而不是启动一个操作系统, 速度就快很多
  2. 资源占用少:
    1. 容器只占用需要的资源, 不占用那些没有用到的资源;虚拟机由于是完整的操作系统, 不可避免要占用所有资源
  3. 共享性好:
    1. 鉴于容器是 进程-level, 所以多个容器之间可以共享资源, 虚拟机只能独享资源

设计原理

Docker是什么

对比之下, 我们发现使用容器技术带来了更多的增益, Docker 就在这样的背景下应运而生

Docker 属于 Linux 容器的一种封装, 提供简单易用的容器使用接口。它是目前最流行的 Linux 容器解决方案

  1. Docker 将应用程序与该程序的依赖, 打包在一个文件里面
  2. 运行这个文件, 就会生成一个虚拟容器
  3. 程序在这个虚拟容器里运行, 就好像在真实的物理机上运行一样, 因此不用担心环境问题
Tip

Docker 的接口相当简单, 用户可以方便地创建和使用容器, 把自己的应用放入容器

容器还可以进行版本管理、复制、分享、修改, 就像管理普通的代码一样

Docker的常见服务

  1. 提供一次性的运行环境:
    1. 常见的是: 将别人的程序带环境拉下来, 在本机运行
  2. 提供elastic的云服务:
    1. 常见的是: 将kubernetes和docker一起做
    2. 原因: Docker 容器可以随开随关, 很适合动态扩容, 扩展性很强
  3. 组建微服务架构:
    1. 常见的是在一台机器上运行多个docker, 组建相关服务
    2. 原因: Docker 是进程级别的封装, 一台机器上可以运行多个进程-level的docker, 它们之间进行串口通信

入门操作

这里不建议直接上手,这更像是笔者自己的cheat sheet。入门学习的话,更建议看给的参考文档:

安装docker

Docker Install for MacOS

以 MacOS 为例:

Bash
1
brew install --cask docker

安装完验证:

Bash
1
2
 docker --version
Docker version 28.0.1, build 068a01e
sudo 权限问题

本质: Docker 需要用户具有 sudo 权限

在 Linux 上,安装之后要手动运行 sudo usermod -aG docker $USER 将 当前用户(CLI中的) 加入 Docker 用户组

但是在 MacOS 上,安装时就要你输入密码,因此安装时已经自动将 sudo 权限赋予了,因此不用管 🚀

几组概念的解析

image 文件

Docker 把应用程序及其依赖,打包在 image 文件里面。只有通过这个文件,才能生成 Docker 容器

  1. image 文件可以看作是容器的模板。Docker 根据 image 文件生成容器的实例
  2. 同一个 image 文件,可以生成多个同时运行的容器实例

image 是二进制文件。实际开发中,一个 image 文件往往通过继承另一个 image 文件,加上一些个性化设置而生成

举例来说,你可以在 Ubuntu 的 image 基础上,往里面加入 Apache 服务器,形成你的 image:

Bash
1
2
3
4
5
# 列出本机的所有 image 文件
docker image ls

# 删除 image 文件
docker image rm [imageName]

image 文件是通用的,一台机器的 image 文件拷贝到另一台机器,照样可以使用。一般来说,为了节省时间,我们应该尽量使用别人制作好的 image 文件,而不是自己制作。即使要定制,也应该基于别人的 image 文件进行加工,而不是从零开始制作

为了方便共享,image 文件制作完成后,可以上传到网上的仓库。Docker 的官方仓库 Docker Hub 是最重要、最常用的 image 仓库

容器文件

image 文件生成的容器实例,本身也是一个文件,称为容器文件

也就是说,一旦容器生成,就会同时存在两个文件:image 文件和容器文件。而且关闭容器并不会删除容器文件,只是容器停止运行而已

Bash
1
2
3
4
5
# 列出本机正在运行的容器
docker container ls

# 列出本机所有容器,包括终止运行的容器
docker container ls --all

上面命令的输出结果之中,包括容器的 ID。很多地方都需要提供这个 ID,比如上一节终止容器运行的 docker container kill 命令

终止运行的容器文件,依然会占据硬盘空间,可以使用 docker container rm 命令删除

Bash
1
docker container rm [containerID]

运行上面的命令之后,再使用 docker container ls --all 命令,就会发现被删除的容器文件已经消失了

Dockerfile文件

学会使用 image 文件以后,接下来的问题就是,如何可以生成 image 文件?如果你要推广自己的软件,势必要自己制作 image 文件

这就需要用到 Dockerfile 文件。它是一个文本文件,用来配置 image。Docker 根据 该文件生成二进制的 image 文件

dockerfile 实战解析: 看看 阮一峰docker中的“十”

TL;DR

梳理一下,按照逻辑关系,不同文件及其对应的含义应该是:

  1. Dockerfile: 用户手写
    1. Dockerfile 作为配置文件,构建相应的 image 文件
  2. Image: 二进制文件
    1. Image 作为可执行文件,用于生成 容器文件
  3. 容器文件: 可执行文件,生成实例
    1. 容器文件,可以在CLI中用相应的指令交互
    2. 生成 运行实例

cheat sheet

image

Bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# (1) 查看本机的image缓存
docker image ls
# (2) 从官方库拉取image到本地
docker image pull library/[imageName]
# (3) 将本地的image推送到远端仓库(个人)
# CLI中本地用户登陆
docker login
# 根据本地仓库(Dockerfile作为配置文件), 生成本地image
docker image tag [LOCAL_IMAGE_NAME]:[Tag] [DockerUsername]/[DEF_REMOTE_IMAGE_NAME]:[Tag]
# 推送至远端的个人仓库
docker image push [DockerUsername]/[repository]:[tag]
# 发布成功后,登录 Docker Hub,就可以看到已经发布的 image 文件了
# (4) 删除本地的某个image缓存
docker image rm [imageName]:[tag]
官方库拉取?

docker image pull 是抓取 image 文件的命令

library/hello-world 是 image 文件在仓库里面的位置,其中 library 是 image 文件所在的组,hello-world是 image 文件的名字

由于 Docker 官方提供的 image 文件,都放在library组里面,所以它的是默认组,可以省略。因此,上面pull的指令中, library/[imageName] 也可以简写成 [imageName]

Dockerfile的格式?
Text Only
1
2
3
4
5
6
FROM node:8.4
COPY . /app
WORKDIR /app
RUN npm install --registry=https://registry.npm.taobao.org
EXPOSE 3000
CMD node demos/01.js
  • -p参数: 容器的 3000 端口映射到本机的 8000 端口
  • -it参数: 容器的 Shell 映射到当前的 Shell,然后你在本机窗口输入的命令,就会传入容器
  • koa-demo:0.0.1: image 文件的名字(如果有标签,还需要提供标签,默认是 latest 标签)
  • /bin/bash: 容器启动后,内部第一个执行的命令。这里是启动 Bash,保证用户可以使用 Shell
  • CMD node demos/01.js: 容器启动后自动执行 node demos/01.js 指令
RUNCMD 的区别与使用
  1. RUN 命令在 image 文件的构建阶段执行,执行结果都会打包进入 image 文件;CMD 命令则是在容器启动后执行
  2. 一个 Dockerfile 可以包含多个RUN命令,但是只能有一个 CMD 命令
  3. 指定 CMD 命令以后,docker container run 命令就不能附加命令了

container

一般查询:

Bash
1
2
3
4
# 列出本机正在运行的容器
docker container ls
# 列出本机所有容器,包括终止运行的容器
docker container ls --all

关闭/资源释放:

Bash
1
2
3
4
5
# 关闭正在运行中的实例
docker container kill [containID]
# 终止运行的容器文件,依然会占据硬盘空间
# 清楚实例对应的所有容器文件 (硬盘释放)
docker container rm [containID]

运行 (单次/交互):

Bash
1
2
3
4
5
# 单次运行, 运行后CLI直接输出, 结束
# eg. hello-world instance
docker container run hello-world
# 交互式终端,运行后可互动, 需要手动关闭
docker container run -it ubuntu bash
Danger
  • 对于 docker container run hello-world:
    • 运行完自动关闭
  • 对于 docker container run -it ubuntu bash:
    • 运行完需要调用 docker container kill [continID] 关闭
一条常见运行指令的写法

我们以ubuntu交互式运行的shell为例:

Bash
1
docker run -it --rm --name ubuntu-container ubuntu:20.04
  • --rm 代表: 容器停止运行(退出)之后,会被立刻删除
    • 等价于: docker container rm [containID]
  • --name 代表: 给容器命名,如果没有加这个参数,那么 docker 会给容器随机起一个名字
  • -it 可交互 Shell 所必须的参数
    • -i 会将容器的 init(主进程,这里是 /bin/bash)的标准输入与 docker 这个程序的标准输入相连接
    • -t 会告知主进程输入为终端(TTY)设备
  • ubuntu:20.04: 指定镜像是ubuntu,版本是20.04
    • 不写版本, 则默认是latest
  • 默认的指令是: /bin/bash
    • 明确写出bash也可以

在执行以上命令之后,你会获得一个 Ubuntu 20.04 的容器环境,退出 Shell 之后容器就会被销毁

Bash
1
docker container run -it ubuntu bash

获得一个 最新版本(可能是24.04) 的Ubuntu容器环境, 立马进入shell, 退出后容器不会被销毁