mirror of
https://github.com/yeasy/docker_practice.git
synced 2026-03-10 11:54:37 +00:00
Add more practices
This commit is contained in:
@@ -28,6 +28,7 @@
|
|||||||
* [利用 commit 理解镜像构成](image/commit.md)
|
* [利用 commit 理解镜像构成](image/commit.md)
|
||||||
* [使用 Dockerfile 定制镜像](image/build.md)
|
* [使用 Dockerfile 定制镜像](image/build.md)
|
||||||
* [Dockerfile 指令详解](image/dockerfile/README.md)
|
* [Dockerfile 指令详解](image/dockerfile/README.md)
|
||||||
|
* [RUN 执行命令](image/dockerfile/run.md)
|
||||||
* [COPY 复制文件](image/dockerfile/copy.md)
|
* [COPY 复制文件](image/dockerfile/copy.md)
|
||||||
* [ADD 更高级的复制文件](image/dockerfile/add.md)
|
* [ADD 更高级的复制文件](image/dockerfile/add.md)
|
||||||
* [CMD 容器启动命令](image/dockerfile/cmd.md)
|
* [CMD 容器启动命令](image/dockerfile/cmd.md)
|
||||||
|
|||||||
181
image/dockerfile/run.md
Normal file
181
image/dockerfile/run.md
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
# RUN 执行命令
|
||||||
|
|
||||||
|
## 基本语法
|
||||||
|
|
||||||
|
```docker
|
||||||
|
RUN <command>
|
||||||
|
RUN ["executable", "param1", "param2"]
|
||||||
|
```
|
||||||
|
|
||||||
|
`RUN` 指令是 Dockerfile 中最常用的指令之一。它在**当前镜像层**之上创建一个新层,执行指定的命令,并提交结果。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 两种格式对比
|
||||||
|
|
||||||
|
### 1. Shell 格式
|
||||||
|
|
||||||
|
```docker
|
||||||
|
RUN apt-get update
|
||||||
|
```
|
||||||
|
|
||||||
|
- **特点**:默认通过 `/bin/sh -c` 执行。
|
||||||
|
- **优势**:可以使用环境变量、管道、重定向等 Shell 特性。
|
||||||
|
- **示例**:
|
||||||
|
```docker
|
||||||
|
RUN echo "Hello" > /test.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Exec 格式
|
||||||
|
|
||||||
|
```docker
|
||||||
|
RUN ["apt-get", "update"]
|
||||||
|
```
|
||||||
|
|
||||||
|
- **特点**:直接调用可执行文件,不经过 Shell。
|
||||||
|
- **优势**:避免 Shell 字符串解析问题,适用于参数中包含特殊字符的情况。
|
||||||
|
- **注意**:无法使用 `$VAR` 环境变量替换(除非显式调用 shell)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 常见最佳实践
|
||||||
|
|
||||||
|
### 1. 组合命令(减少层数)
|
||||||
|
|
||||||
|
每一个 `RUN` 指令都会新建一层镜像。为了减少镜像体积和层数,应使用 `&&` 连接命令。
|
||||||
|
|
||||||
|
**❌ 糟糕的写法**(创建 3 层):
|
||||||
|
|
||||||
|
```docker
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt-get install -y nginx
|
||||||
|
RUN rm -rf /var/lib/apt/lists/*
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ 推荐写法**(创建 1 层):
|
||||||
|
|
||||||
|
```docker
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y nginx && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 清理缓存
|
||||||
|
|
||||||
|
在安装完软件后,立即清除缓存,可以显著减小镜像体积。
|
||||||
|
|
||||||
|
- **Debian/Ubuntu**:
|
||||||
|
```docker
|
||||||
|
RUN apt-get update && apt-get install -y package-bar \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
```
|
||||||
|
- **Alpine**:
|
||||||
|
```docker
|
||||||
|
RUN apk add --no-cache package-bar
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 使用 `set -e` 和 `pipefail`
|
||||||
|
|
||||||
|
默认情况下,管道命令 `cmd1 | cmd2` 只要 `cmd2` 成功,整个 `RUN` 就视为成功。
|
||||||
|
|
||||||
|
**❌ 隐蔽的错误**:
|
||||||
|
|
||||||
|
```docker
|
||||||
|
# 如果下载失败,gzip 可能会报错,但如果不影响后续,构建可能继续
|
||||||
|
RUN wget http://error-url | gzip -d > file
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ 推荐写法**:
|
||||||
|
|
||||||
|
```docker
|
||||||
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
RUN wget http://url | gzip -d > file
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### Q: 为什么 `RUN cd /app` 不生效?
|
||||||
|
|
||||||
|
```docker
|
||||||
|
RUN cd /app
|
||||||
|
RUN touch hello.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果**:`hello.txt` 会出现在根目录 `/`,而不是 `/app`。
|
||||||
|
|
||||||
|
**原因**:每个 `RUN` 都在一个新的 Shell/容器 环境中执行。`cd` 只影响当前 `RUN` 的环境。
|
||||||
|
|
||||||
|
**解决**:使用 `WORKDIR` 指令。
|
||||||
|
|
||||||
|
```docker
|
||||||
|
WORKDIR /app
|
||||||
|
RUN touch hello.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q: 环境变量不生效?
|
||||||
|
|
||||||
|
```docker
|
||||||
|
RUN export MY_VAR=hello
|
||||||
|
RUN echo $MY_VAR
|
||||||
|
```
|
||||||
|
|
||||||
|
**结果**:输出为空。
|
||||||
|
|
||||||
|
**原因**:同上,环境变量只在当前 `RUN` 有效。
|
||||||
|
|
||||||
|
**解决**:使用 `ENV` 指令,或在同一行 `RUN` 中导出。
|
||||||
|
|
||||||
|
```docker
|
||||||
|
ENV MY_VAR=hello
|
||||||
|
RUN echo $MY_VAR
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 高级技巧
|
||||||
|
|
||||||
|
### 1. 使用 BuildKit 的挂载缓存
|
||||||
|
|
||||||
|
BuildKit 支持在 `RUN` 指令中使用 `--mount` 挂载缓存,加速构建。
|
||||||
|
|
||||||
|
```docker
|
||||||
|
# 缓存 apt 包
|
||||||
|
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||||
|
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
||||||
|
apt-get update && apt-get install -y gcc
|
||||||
|
```
|
||||||
|
|
||||||
|
```docker
|
||||||
|
# 缓存 Go 模块
|
||||||
|
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||||
|
go build -o app
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 挂载密钥
|
||||||
|
|
||||||
|
安全地使用 SSH 密钥或 Token,而不将其记录在镜像中。
|
||||||
|
|
||||||
|
```docker
|
||||||
|
RUN --mount=type=secret,id=mysecret \
|
||||||
|
cat /run/secrets/mysecret
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 本章小结
|
||||||
|
|
||||||
|
| 要点 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **作用** | 在新层执行命令 |
|
||||||
|
| **原则** | 合并命令,清理缓存 |
|
||||||
|
| **格式** | Shell (常用) vs Exec |
|
||||||
|
| **陷阱** | `cd` 不持久,环境变量不持久 |
|
||||||
|
| **进阶** | 使用 Cache Mount 加速构建 |
|
||||||
|
|
||||||
|
## 延伸阅读
|
||||||
|
|
||||||
|
- [CMD 容器启动命令](cmd.md):容器启动时的命令
|
||||||
|
- [WORKDIR 指定工作目录](workdir.md):改变目录
|
||||||
|
- [Dockerfile 最佳实践](../../appendix/best_practices.md)
|
||||||
@@ -1,20 +1,83 @@
|
|||||||
# 简介
|
# Kubernetes 简介
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Kubernetes 是 Google 团队发起的开源项目,它的目标是管理跨多个主机的容器,提供基本的部署,维护以及应用伸缩,主要实现语言为 Go 语言。Kubernetes 是:
|
## 什么是 Kubernetes
|
||||||
|
|
||||||
* 易学:轻量级,简单,容易理解
|
Kubernetes(常简称为 K8s)是 Google 开源的容器编排引擎。如果说 Docker 解决了"如何打包和运送集装箱"的问题,那么 Kubernetes 解决的就是"如何管理海量集装箱的调度、运行和维护"的问题。
|
||||||
* 便携:支持公有云,私有云,混合云,以及多种云平台
|
|
||||||
* 可拓展:模块化,可插拔,支持钩子,可任意组合
|
|
||||||
* 自修复:自动重调度,自动重启,自动复制
|
|
||||||
|
|
||||||
Kubernetes 构建于 Google 数十年经验,一大半来源于 Google 生产环境规模的经验。结合了社区最佳的想法和实践。
|
它不仅仅是一个编排系统,更是一个**云原生应用操作系统**。
|
||||||
|
|
||||||
在分布式系统中,部署,调度,伸缩一直是最为重要的也最为基础的功能。Kubernetes 就是希望解决这一序列问题的。
|
> **名字由来**:Kubernetes 在希腊语中意为"舵手"或"飞行员"。K8s 是因为 k 和 s 之间有 8 个字母。
|
||||||
|
|
||||||
Kubernetes 目前在[GitHub](https://github.com/kubernetes/kubernetes)进行维护。
|
---
|
||||||
|
|
||||||
### Kubernetes 能够运行在任何地方!
|
## 为什么需要 Kubernetes
|
||||||
|
|
||||||
虽然 Kubernetes 最初是为 GCE 定制的,但是在后续版本中陆续增加了其他云平台的支持,以及本地数据中心的支持。
|
当我们在单机运行几个容器时,Docker Compose 就足够了。但在生产环境中,我们需要面对:
|
||||||
|
|
||||||
|
- **多主机调度**:容器应该运行在哪台机器上?
|
||||||
|
- **自动恢复**:容器崩溃了怎么办?节点挂了怎么办?
|
||||||
|
- **服务发现**:容器 IP 变了,其他服务怎么找到它?
|
||||||
|
- **负载均衡**:流量大了,如何分发给多个副本?
|
||||||
|
- **滚动更新**:如何不中断服务升级应用?
|
||||||
|
|
||||||
|
Kubernetes 完美解决了这些问题。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 核心概念
|
||||||
|
|
||||||
|
### Pod (豆荚)
|
||||||
|
Kubernetes 的最小调度单位。一个 Pod 可以包含一个或多个紧密协作的容器(共享网络和存储)。就像豌豆荚里的豌豆一样。
|
||||||
|
|
||||||
|
### Node (节点)
|
||||||
|
运行 Pod 的物理机或虚拟机。
|
||||||
|
|
||||||
|
### Deployment (部署)
|
||||||
|
定义应用的期望状态(如:需要 3 个副本,镜像版本为 v1)。K8s 会持续确保当前状态符合期望状态。
|
||||||
|
|
||||||
|
### Service (服务)
|
||||||
|
定义一组 Pod 的访问策略。提供稳定的 Cluster IP 和 DNS 名称,负责负载均衡。
|
||||||
|
|
||||||
|
### Namespace (命名空间)
|
||||||
|
用于多租户资源隔离。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Docker 用户如何过渡
|
||||||
|
|
||||||
|
如果你已经熟悉 Docker,学习 K8s 会很容易:
|
||||||
|
|
||||||
|
| Docker 概念 | Kubernetes 概念 | 说明 |
|
||||||
|
|------------|----------------|------|
|
||||||
|
| Container | Pod | K8s 增加了一层 Pod 包装 |
|
||||||
|
| Volume | PersistentVolume | K8s 的存储更加抽象和强大 |
|
||||||
|
| Network | Service/Ingress| K8s 的网络模型更扁平 |
|
||||||
|
| Compose | Deployment + Service | 声明式配置的理念是一致的 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 架构
|
||||||
|
|
||||||
|
Kubernetes 也是 C/S 架构,由 **Master (控制平面)** 和 **Worker (工作节点)** 组成:
|
||||||
|
|
||||||
|
- **Control Plane**:负责决策(API Server, Scheduler, Controller Manager, etcd)
|
||||||
|
- **Worker Node**:负责干活(Kubelet, Kube-proxy, Container Runtime)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 学习建议
|
||||||
|
|
||||||
|
Kubernetes 的学习曲线较陡峭。建议的学习路径:
|
||||||
|
1. **理解基本概念**:Pod, Deployment, Service
|
||||||
|
2. **动手实践**:使用 Minikube 或 Kind 在本地搭建集群
|
||||||
|
3. **部署应用**:编写 YAML 部署一个无状态应用
|
||||||
|
4. **深入原理**:网络模型、存储机制、调度算法
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 延伸阅读
|
||||||
|
|
||||||
|
- [Minikube 安装](../kubernetes/setup/README.md):本地体验 K8s
|
||||||
|
- [Kubernetes 官网](https://kubernetes.io/):官方文档
|
||||||
|
|||||||
110
network/dns.md
110
network/dns.md
@@ -1,44 +1,114 @@
|
|||||||
# 配置 DNS
|
# 配置 DNS
|
||||||
|
|
||||||
如何自定义配置容器的主机名和 DNS 呢?秘诀就是 Docker 利用虚拟文件来挂载容器的 3 个相关配置文件。
|
## 容器的 DNS 机制
|
||||||
|
|
||||||
在容器中使用 `mount` 命令可以看到挂载信息:
|
Docker 容器的 DNS 配置有两种情况:
|
||||||
|
|
||||||
|
1. **默认 Bridge 网络**:继承宿主机的 DNS 配置(`/etc/resolv.conf`)。
|
||||||
|
2. **自定义网络**(推荐):使用 Docker 嵌入式 DNS 服务器 (Embedded DNS),支持通过**容器名**进行服务发现。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 嵌入式 DNS (Embedded DNS)
|
||||||
|
|
||||||
|
这是 Docker 网络最强大的功能之一。在自定义网络中,容器可以通过"名字"找到彼此,而不需要知道对方的 IP(因为 IP 可能会变)。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ mount
|
# 1. 创建自定义网络
|
||||||
/dev/disk/by-uuid/1fec...ebdf on /etc/hostname type ext4 ...
|
$ docker network create mynet
|
||||||
/dev/disk/by-uuid/1fec...ebdf on /etc/hosts type ext4 ...
|
|
||||||
tmpfs on /etc/resolv.conf type tmpfs ...
|
# 2. 启动容器 web 并加入网络
|
||||||
|
$ docker run -d --name web --network mynet nginx
|
||||||
|
|
||||||
|
# 3. 启动容器 client 并尝试 ping web
|
||||||
|
$ docker run -it --rm --network mynet alpine ping web
|
||||||
|
PING web (172.18.0.2): 56 data bytes
|
||||||
|
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.074 ms
|
||||||
```
|
```
|
||||||
|
|
||||||
这种机制可以让宿主主机 DNS 信息发生更新后,所有 Docker 容器的 DNS 配置通过 `/etc/resolv.conf` 文件立刻得到更新。
|
**原理**:
|
||||||
|
Docker 守护进程在 `127.0.0.11` 运行了一个 DNS 服务器。容器内的 DNS 请求会被转发到这里。如果是容器名,解析为容器 IP;如果是外部域名(如 google.com),转发给上游 DNS。
|
||||||
|
|
||||||
配置全部容器的 DNS ,也可以在 `/etc/docker/daemon.json` 文件中增加以下内容来设置。
|
---
|
||||||
|
|
||||||
|
## 配置 DNS 参数
|
||||||
|
|
||||||
|
如果你需要手动配置容器的 DNS(例如使用内网 DNS 服务器),可以在 `docker run` 中使用以下参数:
|
||||||
|
|
||||||
|
### 1. --dns
|
||||||
|
|
||||||
|
指定 DNS 服务器 IP。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ docker run -it --dns=114.114.114.114 ubuntu cat /etc/resolv.conf
|
||||||
|
nameserver 114.114.114.114
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. --dns-search
|
||||||
|
|
||||||
|
指定 DNS 搜索域。例如设置为 `example.com`,则 `ping host` 会尝试解析 `host.example.com`。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ docker run --dns-search=example.com myapp
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. --hostname (-h)
|
||||||
|
|
||||||
|
设置容器的主机名。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ docker run -h myweb nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 全局 DNS 配置
|
||||||
|
|
||||||
|
如果希望所有容器都使用特定的 DNS 服务器(而不是继承宿主机),可以修改 `/etc/docker/daemon.json`:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns" : [
|
"dns": [
|
||||||
"114.114.114.114",
|
"114.114.114.114",
|
||||||
"8.8.8.8"
|
"8.8.8.8"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
这样每次启动的容器 DNS 自动配置为 `114.114.114.114` 和 `8.8.8.8`。使用以下命令来证明其已经生效。
|
修改后需要重启 Docker 服务:`systemctl restart docker`。
|
||||||
|
|
||||||
```bash
|
---
|
||||||
$ docker run -it --rm ubuntu:24.04 cat etc/resolv.conf
|
|
||||||
|
|
||||||
nameserver 114.114.114.114
|
## 常见问题
|
||||||
nameserver 8.8.8.8
|
|
||||||
```
|
|
||||||
|
|
||||||
如果用户想要手动指定容器的配置,可以在使用 `docker run` 命令启动容器时加入如下参数:
|
### Q: 容器无法解析域名
|
||||||
|
|
||||||
`-h HOSTNAME` 或者 `--hostname=HOSTNAME` 设定容器的主机名,它会被写到容器内的 `/etc/hostname` 和 `/etc/hosts`。但它在容器外部看不到,既不会在 `docker container ls` 中显示,也不会在其他的容器的 `/etc/hosts` 看到。
|
**现象**:`ping www.baidu.com` 失败,但 `ping 8.8.8.8` 成功。
|
||||||
|
|
||||||
`--dns=IP_ADDRESS` 添加 DNS 服务器到容器的 `/etc/resolv.conf` 中,让容器用这个服务器来解析所有不在 `/etc/hosts` 中的主机名。
|
**解决**:
|
||||||
|
1. 宿主机的 `/etc/resolv.conf` 可能有问题(例如使用了本地回环地址 127.0.0.53,特别是 Ubuntu 系统)。Docker 可能会尝试修复,但有时会失败。
|
||||||
|
2. 尝试手动指定 DNS:`docker run --dns 8.8.8.8 ...`
|
||||||
|
3. 检查防火墙是否拦截了 UDP 53 端口。
|
||||||
|
|
||||||
`--dns-search=DOMAIN` 设定容器的搜索域,当设定搜索域为 `.example.com` 时,在搜索一个名为 host 的主机时,DNS 不仅搜索 host,还会搜索 `host.example.com`。
|
### Q: 无法通过容器名通信
|
||||||
|
|
||||||
>注意:如果在容器启动时没有指定最后两个参数,Docker 会默认用主机上的 `/etc/resolv.conf` 来配置容器。
|
**现象**:`ping db` 提示 `bad address 'db'`。
|
||||||
|
|
||||||
|
**原因**:
|
||||||
|
- 你可能在使用**默认的 bridge 网络**。默认 bridge 网络**不支持**通过容器名进行 DNS 解析(这是一个历史遗留设计)。
|
||||||
|
- **解决**:使用自定义网络 (`docker network create ...`)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 本章小结
|
||||||
|
|
||||||
|
| 场景 | DNS 行为 | 备注 |
|
||||||
|
|------|----------|------|
|
||||||
|
| **默认网络** | 继承宿主机 | 不支持容器名解析 |
|
||||||
|
| **自定义网络** | Docker 嵌入式 DNS | ✅ 支持容器名解析 |
|
||||||
|
| **手动指定** | 使用 `--dns` | 覆盖默认配置 |
|
||||||
|
|
||||||
|
## 延伸阅读
|
||||||
|
|
||||||
|
- [网络模式](README.md):Docker 网络概览
|
||||||
|
- [端口映射](port_mapping.md):外部访问
|
||||||
|
|||||||
@@ -1,57 +1,149 @@
|
|||||||
# 映射容器端口到宿主主机的实现
|
# 外部访问容器
|
||||||
|
|
||||||
默认情况下,容器可以主动访问到外部网络的连接,但是外部网络无法访问到容器。
|
## 为什么要映射端口
|
||||||
|
|
||||||
## 容器访问外部实现
|
容器运行在自己的隔离网络环境中(通常是 Bridge 模式)。这意味着:
|
||||||
|
- **容器之间**:可以通过 IP 或容器名(自定义网络)互通。
|
||||||
|
- **宿主机访问容器**:可以通过容器 IP 访问。
|
||||||
|
- **外部网络访问容器**:❌ 默认无法直接访问。
|
||||||
|
|
||||||
容器所有到外部网络的连接,源地址都会被 NAT 成本地系统的 IP 地址。这是使用 `iptables` 的源地址伪装操作实现的。
|
为了让外部(如你的浏览器、其他局域网机器)访问容器内的服务,我们需要将容器的端口**映射**到宿主机的端口。
|
||||||
|
|
||||||
查看主机的 NAT 规则。
|
```
|
||||||
|
外部用户 (Browser)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
宿主机 (localhost:8080)
|
||||||
|
│
|
||||||
|
┌────┴────┐ 端口映射
|
||||||
|
│ Docker │ (8080 -> 80)
|
||||||
|
│ Proxy │
|
||||||
|
└────┬────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
容器 (Class B: 80)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 端口映射方式
|
||||||
|
|
||||||
|
### 1. 指定映射 (-p)
|
||||||
|
|
||||||
|
使用 `-p <宿主机端口>:<容器端口>` 格式。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ sudo iptables -t nat -nL
|
# 将宿主机的 8080 端口映射到容器的 80 端口
|
||||||
...
|
$ docker run -d -p 8080:80 nginx
|
||||||
Chain POSTROUTING (policy ACCEPT)
|
|
||||||
target prot opt source destination
|
|
||||||
MASQUERADE all -- 172.17.0.0/16 !172.17.0.0/16
|
|
||||||
...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
其中,上述规则将所有源地址在 `172.17.0.0/16` 网段,目标地址为其他网段(外部网络)的流量动态伪装为从系统网卡发出。MASQUERADE 跟传统 SNAT 的好处是它能动态从网卡获取地址。
|
此时访问 `http://localhost:8080` 即可看到 Nginx 页面。
|
||||||
|
|
||||||
## 外部访问容器实现
|
**多种格式**:
|
||||||
|
|
||||||
容器允许外部访问,可以在 `docker run` 时候通过 `-p` 或 `-P` 参数来启用。
|
| 格式 | 含义 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| `ip:hostPort:containerPort` | 绑定指定 IP 的特定端口 | `-p 127.0.0.1:8080:80` (仅本机访问) |
|
||||||
|
| `ip::containerPort` | 绑定指定 IP 的随机端口 | `-p 127.0.0.1::80` |
|
||||||
|
| `hostPort:containerPort` | 绑定所有 IP (0.0.0.0) 的特定端口 | `-p 8080:80` (默认) |
|
||||||
|
| `containerPort` | 绑定所有 IP 的随机端口 | `-p 80` |
|
||||||
|
|
||||||
不管用那种办法,其实也是在本地的 `iptable` 的 nat 表中添加相应的规则。
|
### 2. 随机映射 (-P)
|
||||||
|
|
||||||
使用 `-P` 时:
|
使用 `-P` (大写) 参数,Docker 会随机映射 Dockerfile 中 `EXPOSE` 指令暴露的所有端口到宿主机的高端口(49000-49900)。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ iptables -t nat -nL
|
$ docker run -d -P nginx
|
||||||
...
|
|
||||||
Chain DOCKER (2 references)
|
|
||||||
target prot opt source destination
|
|
||||||
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:49153 to:172.17.0.2:80
|
|
||||||
```
|
```
|
||||||
|
|
||||||
使用 `-p 80:80` 时:
|
查看映射结果:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ iptables -t nat -nL
|
$ docker ps
|
||||||
Chain DOCKER (2 references)
|
CONTAINER ID PORTS
|
||||||
target prot opt source destination
|
abc123456 0.0.0.0:49153->80/tcp
|
||||||
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80
|
|
||||||
```
|
```
|
||||||
|
|
||||||
注意:
|
此时 Nginx 被映射到了宿主机的 49153 端口。
|
||||||
|
|
||||||
* 这里的规则映射了 `0.0.0.0`,意味着将接受主机来自所有接口的流量。用户可以通过 `-p IP:host_port:container_port` 或 `-p IP::port` 来指定允许访问容器的主机上的 IP、接口等,以制定更严格的规则。
|
---
|
||||||
|
|
||||||
* 如果希望永久绑定到某个固定的 IP 地址,可以在 Docker 配置文件 `/etc/docker/daemon.json` 中添加如下内容。
|
## 查看端口映射
|
||||||
|
|
||||||
```json
|
### docker port
|
||||||
{
|
|
||||||
"ip": "0.0.0.0"
|
```bash
|
||||||
}
|
$ docker port mycontainer
|
||||||
|
80/tcp -> 0.0.0.0:8080
|
||||||
|
80/tcp -> [::]:8080
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### docker ps
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ docker ps
|
||||||
|
CONTAINER ID IMAGE PORTS NAMES
|
||||||
|
abc123456 nginx 0.0.0.0:8080->80/tcp web
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 最佳实践与安全
|
||||||
|
|
||||||
|
### 1. 限制监听 IP
|
||||||
|
|
||||||
|
默认情况下,`-p 8080:80` 会监听 `0.0.0.0:8080`,这意味着任何人只要能连接你的宿主机 IP,就能访问该服务。
|
||||||
|
|
||||||
|
如果不希望对外暴露(例如数据库服务),应绑定到 `127.0.0.1`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 仅允许本机访问
|
||||||
|
$ docker run -d -p 127.0.0.1:3306:3306 mysql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 避免端口冲突
|
||||||
|
|
||||||
|
如果宿主机 8080 已经被占用了,容器将无法启动。
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
- 更换宿主机端口:`-p 8081:80`
|
||||||
|
- 让 Docker 自动分配:`-p 80`
|
||||||
|
|
||||||
|
### 3. UDP 映射
|
||||||
|
|
||||||
|
默认是 TCP 协议。如果要映射 UDP 服务(如 DNS, Syslog):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ docker run -d -p 53:53/udp dns-server
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 实现原理
|
||||||
|
|
||||||
|
Docker 使用 `docker-proxy` 进程(用户态)或 `iptables` DNAT 规则(内核态)来实现端口转发。
|
||||||
|
|
||||||
|
当流量到达宿主机端口时,iptables 规则将其目标地址修改为容器 IP 并转发:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 简化的 iptables 逻辑
|
||||||
|
iptables -t nat -A DOCKER -p tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:80
|
||||||
|
```
|
||||||
|
|
||||||
|
这也是为什么你在容器内部看到的访问来源 IP 通常是网关 IP(如 172.17.0.1),而不是真实的外部 Client IP(除非使用 host 网络模式)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 本章小结
|
||||||
|
|
||||||
|
| 要点 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **-p** | 指定端口映射(常用),如 `8080:80` |
|
||||||
|
| **-P** | 随机映射所有 EXPOSE 的端口 |
|
||||||
|
| **安全性** | 默认监听所有 IP,敏感服务应绑定 `127.0.0.1` |
|
||||||
|
| **查看** | 使用 `docker port` 或 `docker ps` |
|
||||||
|
|
||||||
|
## 延伸阅读
|
||||||
|
|
||||||
|
- [EXPOSE 指令](../image/dockerfile/expose.md):在 Dockerfile 中声明端口
|
||||||
|
- [网络模式](README.md):Host 模式不需要端口映射
|
||||||
|
|||||||
@@ -1,98 +1,126 @@
|
|||||||
# Docker Hub
|
# Docker Hub
|
||||||
|
|
||||||
目前 Docker 官方维护了一个公共仓库 [Docker Hub](https://hub.docker.com/),其中已经包括了数量超过 [10,000,000](https://hub.docker.com/search/?type=image) 的镜像。大部分需求都可以通过在 Docker Hub 中直接下载镜像来实现。
|
## 什么是 Docker Hub
|
||||||
|
|
||||||
## 注册
|
[Docker Hub](https://hub.docker.com/) 是 Docker 官方维护的公共镜像仓库,也是全球最大的容器镜像库。
|
||||||
|
|
||||||
你可以在 https://hub.docker.com 免费注册一个 Docker 账号。
|
它提供了:
|
||||||
|
- **官方镜像**:由 Docker 官方和软件厂商(如 Nginx, MySQL, Node.js)维护的高质量镜像。
|
||||||
|
- **个人/组织仓库**:用户可以上传自己的镜像。
|
||||||
|
- **自动构建**:与 GitHub/Bitbucket 集成(需付费)。
|
||||||
|
- **Webhooks**:镜像更新时触发回调。
|
||||||
|
|
||||||
## 登录
|
---
|
||||||
|
|
||||||
可以通过执行 `docker login` 命令交互式的输入用户名及密码来完成在命令行界面登录 Docker Hub。
|
## 核心功能
|
||||||
|
|
||||||
你可以通过 `docker logout` 退出登录。
|
### 1. 搜索镜像
|
||||||
|
|
||||||
## 拉取镜像
|
除了网页搜索,也可以使用命令行:
|
||||||
|
|
||||||
你可以通过 `docker search` 命令来查找官方仓库中的镜像,并利用 `docker pull` 命令来将它下载到本地。
|
|
||||||
|
|
||||||
例如以 `centos` 为关键词进行搜索:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ docker search centos
|
$ docker search centos
|
||||||
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
|
NAME DESCRIPTION STARS OFFICIAL
|
||||||
centos The official build of CentOS. 6449 [OK]
|
centos The official build of CentOS. 7000+ [OK]
|
||||||
ansible/centos7-ansible Ansible on Centos7 132 [OK]
|
|
||||||
consol/centos-xfce-vnc Centos container with "headless" VNC session… 126 [OK]
|
|
||||||
jdeathe/centos-ssh OpenSSH / Supervisor / EPEL/IUS/SCL Repos - … 117 [OK]
|
|
||||||
centos/systemd systemd enabled base container. 96 [OK]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
可以看到返回了很多包含关键字的镜像,其中包括镜像名字、描述、收藏数(表示该镜像的受关注程度)、是否官方创建(`OFFICIAL`)、是否自动构建 (`AUTOMATED`)。
|
> **技巧**:始终优先使用 `OFFICIAL` 标记为 `[OK]` 的镜像,安全性更有保障。
|
||||||
|
|
||||||
根据是否是官方提供,可将镜像分为两类。
|
### 2. 拉取镜像
|
||||||
|
|
||||||
一种是类似 `centos` 这样的镜像,被称为基础镜像或根镜像。这些基础镜像由 Docker 公司创建、验证、支持、提供。这样的镜像往往使用单个单词作为名字。
|
|
||||||
|
|
||||||
还有一种类型,比如 `ansible/centos7-ansible` 镜像,它是由 Docker Hub 的注册用户创建并维护的,往往带有用户名称前缀。可以通过前缀 `username/` 来指定使用某个用户提供的镜像,比如 ansible 用户。
|
|
||||||
|
|
||||||
另外,在查找的时候通过 `--filter=stars=N` 参数可以指定仅显示收藏数量为 `N` 以上的镜像。
|
|
||||||
|
|
||||||
下载官方 `centos` 镜像到本地。
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ docker pull centos
|
$ docker pull nginx:alpine
|
||||||
Using default tag: latest
|
|
||||||
latest: Pulling from library/centos
|
|
||||||
7a0437f04f83: Pull complete
|
|
||||||
Digest: sha256:5528e8b1b1719d34604c87e11dcd1c0a20bedf46e83b5632cdeac91b8c04efc1
|
|
||||||
Status: Downloaded newer image for centos:latest
|
|
||||||
docker.io/library/centos:latest
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 推送镜像
|
### 3. 推送镜像
|
||||||
|
|
||||||
用户也可以在登录后通过 `docker push` 命令来将自己的镜像推送到 Docker Hub。
|
需要先登录:
|
||||||
|
|
||||||
以下命令中的 `username` 请替换为你的 Docker 账号用户名。
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ docker tag ubuntu:24.04 username/ubuntu:24.04
|
$ docker login
|
||||||
|
# 输入用户名和密码
|
||||||
$ docker image ls
|
|
||||||
|
|
||||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
|
||||||
ubuntu 24.04 5a81c4b8502e 6 days ago 78.3MB
|
|
||||||
username/ubuntu 24.04 5a81c4b8502e 6 days ago 78.3MB
|
|
||||||
|
|
||||||
$ docker push username/ubuntu:24.04
|
|
||||||
|
|
||||||
$ docker search username
|
|
||||||
|
|
||||||
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
|
|
||||||
username/ubuntu
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 自动构建
|
打标签并推送:
|
||||||
|
|
||||||
> 2021 年 7 月 26 日之后,该项功能仅限[付费用户](https://www.docker.com/blog/changes-to-docker-hub-autobuilds/)使用。
|
```bash
|
||||||
|
# 1. 标记镜像
|
||||||
|
$ docker tag myapp:v1 username/myapp:v1
|
||||||
|
|
||||||
自动构建(`Automated Builds`)可以自动触发构建镜像,方便升级镜像。
|
# 2. 推送
|
||||||
|
$ docker push username/myapp:v1
|
||||||
|
```
|
||||||
|
|
||||||
有时候,用户构建了镜像,安装了某个软件,当软件发布新版本则需要手动更新镜像。
|
---
|
||||||
|
|
||||||
而自动构建允许用户通过 Docker Hub 指定跟踪一个目标网站(支持 [GitHub](https://github.com) 或 [BitBucket](https://bitbucket.org))上的项目,一旦项目发生新的提交 (`commit`)或者创建了新的标签(`tag`),Docker Hub 会自动构建镜像并推送到 Docker Hub 中。
|
## 限制与配额(重要)
|
||||||
|
|
||||||
要配置自动构建,包括如下的步骤:
|
### 镜像拉取限制 (Rate Limiting)
|
||||||
|
|
||||||
* 登录 Docker Hub;
|
自 2020 年 11 月起,Docker Hub 对匿名和免费用户实施了拉取速率限制:
|
||||||
|
|
||||||
* 在 Docker Hub 点击右上角头像,在账号设置(`Account Settings`)中关联(`Linked Accounts`)目标网站;
|
| 用户类型 | 限制 |
|
||||||
|
|---------|------|
|
||||||
|
| **匿名用户** (未登录) | 每 6 小时 100 次请求 |
|
||||||
|
| **免费账户** (已登录) | 每 6 小时 200 次请求 |
|
||||||
|
| **Pro/Team 账户** | 无限制 |
|
||||||
|
|
||||||
* 在 Docker Hub 中新建或选择已有的仓库,在 `Builds` 选项卡中选择 `Configure Automated Builds`;
|
> **提示**:如果在 CI/CD 环境中遇到 `toomanyrequests` 错误,建议:
|
||||||
|
> 1. 在 CI 中配置 `docker login`
|
||||||
|
> 2. 使用国内镜像加速器
|
||||||
|
> 3. 搭建私有仓库代理
|
||||||
|
|
||||||
* 选取一个目标网站中的项目(需要含 `Dockerfile`)和分支;
|
---
|
||||||
|
|
||||||
* 指定 `Dockerfile` 的位置,并保存。
|
## 安全最佳实践
|
||||||
|
|
||||||
之后,可以在 Docker Hub 的仓库页面的 `Timeline` 选项卡中查看每次构建的状态。
|
### 1. 启用 2FA (双因素认证)
|
||||||
|
|
||||||
|
在 Account Settings -> Security 中启用 2FA,保护账号安全。启用后,CLI 登录需要使用 **Access Token** 而非密码。
|
||||||
|
|
||||||
|
### 2. 使用 Access Token
|
||||||
|
|
||||||
|
不要在脚本或 CI/CD 中直接使用登录密码。
|
||||||
|
1. 在 Docker Hub -> Account Settings -> Security -> Access Tokens 创建 Token。
|
||||||
|
2. 使用 Token 作为密码登录:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ docker login -u username -p dckr_pat_xxxxxxx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 关注镜像漏洞
|
||||||
|
|
||||||
|
Docker Hub 会对官方镜像和付费用户的镜像进行安全扫描。在镜像标签页可以看到漏洞扫描结果。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Webhooks
|
||||||
|
|
||||||
|
当镜像被推送时,可以自动触发 HTTP 回调(例如通知 CI 系统部署)。
|
||||||
|
|
||||||
|
**配置方法**:
|
||||||
|
仓库页面 -> Webhooks -> Create Webhook。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 自动构建 (Automated Builds)
|
||||||
|
|
||||||
|
> ⚠️ 目前仅限付费用户 (Pro/Team) 使用。
|
||||||
|
|
||||||
|
链接 GitHub/Bitbucket 仓库后,当代码有提交或打标签时,Docker Hub 会自动运行构建。这保证了镜像总是与代码同步,且由可信的官方环境构建。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 本章小结
|
||||||
|
|
||||||
|
| 功能 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **官方镜像** | 优先使用的基础镜像 |
|
||||||
|
| **拉取限制** | 匿名 100次/6h,登录 200次/6h |
|
||||||
|
| **安全** | 推荐开启 2FA 并使用 Access Token |
|
||||||
|
| **自动化** | 支持 Webhooks 和自动构建 |
|
||||||
|
|
||||||
|
## 延伸阅读
|
||||||
|
|
||||||
|
- [私有仓库](registry.md):搭建自己的 Registry
|
||||||
|
- [镜像加速器](../install/mirror.md):加速下载
|
||||||
|
|||||||
Reference in New Issue
Block a user