Docker API入门与实践
- 2026-03-12 22:12:00
- 丁国栋
- 原创 18
本文目录是通过PHP编程语言来控制Docker Swarm集群内容器的生命周期:创建、启动、停止、删除容器。
注:不知道是不是“知识诅咒“的原因,Docker 的官方文档做的那么复杂,那么难用,让一个经验丰富的运维、开发人员也无法很快的理清文档的结构以及要找的信息。在我看来,往往一个公司、团队能做好一个产品,但维护好产品的文档和做好搜索工作却不是一件简单的事情。本来文档的目的就是让人使用产品过程中遇到问题可以自行解决,文档做不好就得强迫用户花费耐心和时间来解决他们的问题。既然费尽心思和精力做了文档,为啥不把文档做得方便使用、搜索更精准和快速呢?AI在这方面是挺有优势的,这也是为什么越来越多的人愿意与AI对话来代替搜索,就是人不愿意等待、搜索和处理,想吃现成的。人就是懒惰、就是贪婪,这是人的本性、是人之常情。想要让人逆而行之只会让人们趋利避害,逼迫他们选择更受欢迎的解决方案。
问:2375这个端口是必须开的么?Docker官方文档是怎么说的?给出权威参考
2375端口不是必须开的,Docker官方强烈建议不要在生产环境中直接暴露2375端口。
1. 官方态度:默认关闭,严禁裸奔
Docker默认只监听Unix Socket(/var/run/docker.sock),默认情况下2375端口是关闭的。如果你在配置中看到 -H tcp://0.0.0.0:2375,这属于非默认的、主动开启的配置。
官方核心警告:直接暴露2375端口(即不启用TLS加密)是极其危险的。因为Docker API本身没有内置的身份验证机制,一旦暴露,任何能访问该端口的人都可以像root用户一样控制你的Docker守护进程,这相当于把服务器的root权限拱手让人。因此要启用远程管理,必须配置CA证书、服务端证书和客户端证书,实现双向认证。
参考:
2. 安全替代方案(官方推荐)
如果你确实需要远程管理Docker,官方推荐使用2376端口配合TLS双向认证。
| 端口 | 协议 | 安全性 | 官方建议 |
|---|---|---|---|
| 2375 | HTTP (未加密) | 极低 | 严禁在生产环境使用。仅限开发环境或受信任的内网隔离环境。 |
| 2376 | HTTPS (TLS加密) | 高 | 生产环境唯一推荐。必须配置CA证书、服务端证书和客户端证书,实现双向认证。 |
3. 给你的PHP代码安全建议
结合你之前的PHP编程需求,建议采用以下安全策略:
- 首选方案(最安全):PHP程序与Docker运行在同一台机器上,直接通过Unix Socket文件(
/var/run/docker.sock)通信,完全不需要开端口。 - 次选方案(远程管理):如果PHP必须远程连接,请务必配置TLS(使用2376端口),并在PHP代码中加载客户端证书进行认证。
注:实际上,php应用程序最期望对接的是RESTful API,而不是 Socket 文件,所以配置 TLS 才是可行的方案。
问: port 2377 是啥?我看Docker swarm开启后就有
2377 端口是 Docker Swarm 集群的“管理端口”,专门用于集群节点之间的通信和集群状态管理。它与你之前提到的 2375 端口(Docker 守护进程 API)在用途和安全性上完全不同。
1. 核心作用:集群的“神经中枢”
当你在主机上执行 docker swarm init 时,Docker 引擎会自动在 2377 端口 启动一个 Swarm 管理器。这个端口负责处理所有与集群管理相关的核心事务:
- 节点加入:当你执行
docker swarm join命令时,工作节点(Worker)正是通过这个端口连接到管理节点(Manager)来注册自己的。 - 集群状态同步:管理节点之间通过此端口进行 Raft 共识算法的通信,以维护集群状态的一致性(如 Leader 选举、服务编排信息同步)。
- API 端点:它也是 Swarm 模式下的 API 端点,用于接收来自
docker service等命令的请求。
2. 2377 vs. 2375:关键区别
为了让你更清晰地理解,这里对比一下这两个端口:
| 端口 | 2377 (Swarm 管理端口) | 2375 (Docker API 端口) |
|---|---|---|
| 用途 | 集群管理:节点发现、Raft 共识、服务编排 | 容器管理:创建/启动/停止容器、拉取镜像 |
| 通信对象 | 集群内的 Manager 节点 和 Worker 节点 | 外部客户端(如你的 PHP 程序、Docker CLI) |
| 默认状态 | 开启 Swarm 模式后 自动监听 | 默认 关闭,需手动配置 daemon.json 开启 |
| 安全建议 | 建议仅对 集群内网 开放,防止外部攻击 | 严禁 在生产环境直接暴露(无认证) |
3. 官方文档与权威参考
Docker 官方文档明确指出,2377 端口是 Swarm 模式正常运行所必需的。在集群搭建时,必须确保所有节点(尤其是 Manager 节点)之间的 2377 端口是互通的,否则节点无法加入集群或无法进行状态同步。
总结:2375 是你用来“控制” Docker 的端口,而 2377 是 Docker Swarm 集群用来“自我管理”的端口。在 PHP 编程中,你通常只需要关注 2375 端口(或更安全的 2376 端口)来发送 API 指令,而 2377 端口由 Docker 集群内部自动维护。
问: 具体如何配置TLS加密的Docker Remote API?请提供详细的步骤和PHP代码示例
配置 Docker Remote API 的 TLS 加密涉及服务端配置(Docker 守护进程)和客户端配置(PHP 代码)两部分。以下是详细的步骤和 PHP 代码示例。
一、服务端配置:生成证书并启用 TLS
在 Docker 服务器上执行以下步骤,生成 CA 根证书、服务器证书和客户端证书。
1. 生成证书文件
建议在 /etc/docker/tls 目录下操作。
# 创建证书目录
mkdir -p /etc/docker/tls && cd /etc/docker/tls
# 1. 生成 CA 根证书(用于签发其他证书)
openssl genrsa -out ca-key.pem 4096
openssl req -new -x509 -days 365 -key ca-key.pem -out ca.pem -subj "/CN=my-ca"
# 2. 生成服务器证书(用于 Docker 守护进程)
openssl genrsa -out server-key.pem 4096
openssl req -new -key server-key.pem -out server.csr -subj "/CN=$(hostname)"
# 使用 CA 签名服务器证书
openssl x509 -req -days 365 -in server.csr -CA ca.pem -CAkey ca-key.pem -out server-cert.pem
# 3. 生成客户端证书(用于 PHP 程序认证)
openssl genrsa -out client-key.pem 4096
openssl req -new -key client-key.pem -out client.csr -subj "/CN=client"
# 使用 CA 签名客户端证书
openssl x509 -req -days 365 -in client.csr -CA ca.pem -CAkey ca-key.pem -out client-cert.pem
# 清理临时文件
rm -f *.csr
CA证书和CA签发的证书在有效年限方面遵循“根短枝长”的原则,即根证书(CA)的有效期通常比它签发的证书(叶证书)更短。这是为了降低根证书泄露带来的长期风险。
以下是具体的年限规定和逻辑:
1. 根证书(CA Certificate)
-
年限趋势:越来越短。
-
具体规定:
- 传统年限:过去通常为 10年 或 20年。
- 现代标准:根据 CA/Browser Forum(行业标准组织)的最新要求,根证书的有效期不应超过 398天(约13个月)。例如,Let's Encrypt 的根证书有效期就是 398 天。
-
原因:缩短根证书寿命是为了提高安全性。如果根证书私钥泄露,较短的寿命意味着攻击者能利用它的时间窗口更小。
2. 中间证书(Intermediate Certificate)
-
年限:通常为 5年 到 10年。
-
作用:作为根证书和叶证书之间的缓冲层。根证书离线保存,中间证书负责日常签发,这样即使中间证书泄露,也只需吊销中间证书,无需动用到根证书。
3. 叶证书(Leaf Certificate / End-entity Certificate)
-
年限:通常为 1年 或 2年。
-
具体规定:
- 公开信任的证书(如网站HTTPS):根据行业标准,有效期已缩短至 398天(约13个月)。
- 私有/内部证书(如你的Docker TLS证书):通常设置为 1年 或 2年,但具体年限由签发机构(CA)的策略决定。
-
原因:频繁轮换可以降低私钥泄露的风险,并强制定期进行安全审计。
4. 总结与建议
对于你正在配置的 Docker TLS 证书,建议遵循以下原则:
- 根证书(ca.pem):建议设置为 1年(365天)。虽然私有环境没有强制要求,但遵循“短命”原则更安全。
- 服务器/客户端证书:建议设置为 1年(365天)或更短。这样每年都需要重新签发,可以确保只有当前有效的设备才能连接。
自签发的证书没有这种限制,理论上可以设置为 100 年甚至 1000 年。但强烈不建议这样做,因为这会带来巨大的安全风险。
1. 为什么可以设置超长年限?
-
技术层面:OpenSSL 等工具在生成证书时,
-days参数接受任意正整数。只要系统时间戳不溢出,设置 100 年(36500 天)在语法上是完全可行的。 -
无监管:自签证书(Self-signed Certificate)仅用于内部系统(如你的 Docker 集群),不依赖公共信任链(如浏览器信任的 CA)。因此,它不受行业标准(如 CA/Browser Forum)的强制约束。
2. 为什么强烈不建议设置 100 年?
虽然技术上可行,但设置超长年限会带来以下致命问题:
- 私钥泄露风险:证书的核心是私钥。如果私钥泄露,攻击者可以伪装成你的服务器或客户端。如果证书有效期为 100 年,意味着这个泄露的私钥在 100 年内都能被利用,你无法通过证书过期来自然淘汰它。
- 无法强制轮换:证书过期是强制系统管理员进行安全审计和密钥轮换的“硬性机制”。如果设置为 100 年,管理员可能会忘记更换证书,导致系统长期使用可能已经泄露或不安全的密钥。
- 吊销机制失效:自签证书通常没有完善的吊销列表(CRL)机制。一旦私钥泄露,除了手动在所有客户端删除信任,几乎没有其他补救措施。而短有效期(如 1 年)可以让你在证书过期后自然“重置”安全状态。
3. 最佳实践建议
对于你的 Docker TLS 证书,建议遵循以下原则:
- 根证书(CA):设置为 1-2 年。虽然它是自签的,但它是信任链的源头,一旦泄露后果严重。
- 服务器/客户端证书:设置为 1 年 或更短。这样每年都需要重新签发,可以确保只有当前有效的设备才能连接。
2. 配置 Docker 守护进程
编辑 /etc/docker/daemon.json 文件,配置 Docker 监听 2376 端口并启用 TLS 验证。
{
"tlsverify": true,
"tlscacert": "/etc/docker/tls/ca.pem",
"tlscert": "/etc/docker/tls/server-cert.pem",
"tlskey": "/etc/docker/tls/server-key.pem",
"hosts": ["tcp://0.0.0.0:2376", "unix:///var/run/docker.sock"]
}
重要提示:如果系统使用 systemd 管理 Docker,直接在 daemon.json 中配置 hosts 可能不生效。此时需要编辑 systemd 服务文件(如 /lib/systemd/system/docker.service),在 ExecStart 行末尾添加 -H tcp://0.0.0.0:2376。
# 重启 Docker 服务
systemctl daemon-reload
systemctl restart docker
注:使用 /etc/docker/daemon.json 文件和使用 systemd 配置都可以,但不要同时使用,会导致服务无法启动。例如选择了在配置文件定义启动参数,就不要再使用 systemd 配置,反之亦然。除非应用程序有优先级设计或者冲突检测等机制。
二、PHP 客户端代码示例
PHP 连接 TLS 加密的 Docker API 主要有两种方式:原生 cURL 和 Docker PHP SDK。
方式 1:使用原生 cURL(推荐用于简单操作)
这种方式直接使用 PHP 的 cURL 扩展,通过 HTTPS 发送请求,并指定证书路径。
<?php
$docker_host = 'https://your-docker-server-ip:2376';
$cert_path = '/path/to/your/certs/'; // 存放客户端证书的目录
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $docker_host . '/version', // 测试连接
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => true, // 验证服务端证书
CURLOPT_SSL_VERIFYHOST => 2, // 严格验证主机名
CURLOPT_CAINFO => $cert_path . 'ca.pem', // CA 根证书
CURLOPT_SSLCERT => $cert_path . 'client-cert.pem', // 客户端证书
CURLOPT_SSLKEY => $cert_path . 'client-key.pem', // 客户端私钥
]);
$response = curl_exec($ch);
if (curl_errno($ch)) {
die('Error: ' . curl_error($ch));
}
curl_close($ch);
echo "Docker Version Info:\n";
echo $response;
?>
方式 2:使用 Docker PHP SDK(推荐用于复杂管理)
使用 Composer 安装 docker-php/docker-php 库,它提供了面向对象的接口。
安装依赖:
composer require docker-php/docker-php
PHP 代码示例:
<?php
require_once 'vendor/autoload.php';
use Docker\Docker;
use Docker\DockerClientFactory;
// 配置连接参数
$config = [
'remote_socket' => 'tcp://your-docker-server-ip:2376',
'ssl' => true,
'ssl_cert' => '/path/to/client-cert.pem',
'ssl_key' => '/path/to/client-key.pem',
'ssl_ca' => '/path/to/ca.pem',
'stream_context_options' => [
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
'allow_self_signed' => false,
]
]
];
try {
$client = DockerClientFactory::create($config);
$docker = Docker::create($client);
// 示例:列出所有容器
$containers = $docker->containerList();
foreach ($containers as $container) {
echo $container->getNames()[0] . "\n";
}
} catch (Exception $e) {
die('Connection failed: ' . $e->getMessage());
}
?>
三、关键注意事项
- 端口区别:TLS 加密使用 2376 端口,而非不安全的 2375 端口。
- 证书权限:确保生成的证书文件(特别是私钥
.pem)权限设置为600(仅所有者可读写),防止泄露。 - 防火墙:确保服务器的 2376 端口在防火墙中已放行。
- PHP 环境:确保 PHP 已安装并启用
openssl和curl扩展。
问:curl: (56) OpenSSL SSL_read: OpenSSL/3.0.13: error:0A00045C:SSL routines::tlsv13 alert certificate required, errno 0
这个错误表明服务器(Docker 守护进程)要求客户端提供证书,但客户端(你的 cURL/PHP)没有正确发送证书或证书不被信任。
一、问题诊断与解决步骤
步骤 1:验证证书路径和权限
# 检查证书文件是否存在
ls -la /path/to/your/certs/
# 应该看到:ca.pem, client-cert.pem, client-key.pem
# 检查权限(私钥必须600)
chmod 600 client-key.pem
chmod 644 client-cert.pem ca.pem
# 验证证书有效性
openssl x509 -in client-cert.pem -text | grep "Subject:"
# 应显示:Subject: CN = client
步骤 2:测试 cURL 命令行
在服务器上先用 cURL 命令行测试:
# 基本测试(应失败)
curl https://localhost:2376/version
# 带证书测试
curl --cert client-cert.pem \
--key client-key.pem \
--cacert ca.pem \
https://localhost:2376/version
如果命令行成功但 PHP 失败,继续看步骤 3。
步骤 3:PHP 代码调试
<?php
$docker_host = 'https://localhost:2376';
$cert_path = '/etc/docker/tls/';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $docker_host . '/version',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_VERBOSE => true, // 启用详细输出
CURLOPT_STDERR => fopen('/tmp/curl_debug.log', 'w+'), // 输出到文件
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_CAINFO => $cert_path . 'ca.pem',
CURLOPT_SSLCERT => $cert_path . 'client-cert.pem',
CURLOPT_SSLKEY => $cert_path . 'client-key.pem',
CURLOPT_SSLKEYPASSWD => null, // 如果私钥有密码,填在这里
CURLOPT_SSLCERTTYPE => 'PEM', // 明确指定类型
CURLOPT_SSLKEYTYPE => 'PEM',
]);
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo "cURL Error: " . curl_error($ch) . "\n";
echo "Error Code: " . curl_errno($ch) . "\n";
// 输出调试信息
echo "Debug info saved to /tmp/curl_debug.log\n";
echo file_get_contents('/tmp/curl_debug.log');
} else {
echo "Success:\n";
print_r(json_decode($response, true));
}
curl_close($ch);
?>
二、完整的 PHP TLS 连接类
<?php
class DockerTLSClient {
private $host;
private $certPath;
private $ch;
public function __construct($host = 'https://localhost:2376', $certPath = '/etc/docker/tls/') {
$this->host = rtrim($host, '/');
$this->certPath = rtrim($certPath, '/') . '/';
$this->validateCertificates();
}
private function validateCertificates() {
$requiredFiles = ['ca.pem', 'client-cert.pem', 'client-key.pem'];
foreach ($requiredFiles as $file) {
$fullPath = $this->certPath . $file;
if (!file_exists($fullPath)) {
throw new Exception("Certificate file not found: $fullPath");
}
if (!is_readable($fullPath)) {
throw new Exception("Certificate file not readable: $fullPath");
}
}
// 检查私钥权限
if (substr(sprintf('%o', fileperms($this->certPath . 'client-key.pem')), -4) !== '0600') {
chmod($this->certPath . 'client-key.pem', 0600);
}
}
public function request($method, $endpoint, $data = null) {
$this->ch = curl_init();
$options = [
CURLOPT_URL => $this->host . $endpoint,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => strtoupper($method),
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_CAINFO => $this->certPath . 'ca.pem',
CURLOPT_SSLCERT => $this->certPath . 'client-cert.pem',
CURLOPT_SSLKEY => $this->certPath . 'client-key.pem',
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30,
];
if ($data !== null) {
$options[CURLOPT_POSTFIELDS] = is_string($data) ? $data : json_encode($data);
}
curl_setopt_array($this->ch, $options);
$response = curl_exec($this->ch);
$httpCode = curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
if (curl_errno($this->ch)) {
$error = curl_error($this->ch);
$errno = curl_errno($this->ch);
curl_close($this->ch);
throw new Exception("cURL Error ($errno): $error");
}
curl_close($this->ch);
if ($httpCode >= 400) {
throw new Exception("HTTP $httpCode: " . $response);
}
return json_decode($response, true);
}
// 测试连接
public function testConnection() {
try {
$result = $this->request('GET', '/version');
echo "Connection successful!\n";
echo "Docker API Version: " . ($result['ApiVersion'] ?? 'N/A') . "\n";
echo "Docker Version: " . ($result['Version'] ?? 'N/A') . "\n";
return true;
} catch (Exception $e) {
echo "Connection failed: " . $e->getMessage() . "\n";
return false;
}
}
// 容器操作示例
public function listContainers($all = false) {
$endpoint = '/containers/json' . ($all ? '?all=1' : '');
return $this->request('GET', $endpoint);
}
public function createContainer($config) {
return $this->request('POST', '/containers/create', $config);
}
public function startContainer($containerId) {
return $this->request('POST', "/containers/$containerId/start");
}
}
// 使用示例
try {
$docker = new DockerTLSClient('https://your-server-ip:2376', '/path/to/certs/');
if ($docker->testConnection()) {
// 列出容器
$containers = $docker->listContainers();
print_r($containers);
// 创建容器示例
$containerConfig = [
'Image' => 'nginx:alpine',
'HostConfig' => [
'PortBindings' => [
'80/tcp' => [['HostPort' => '8080']]
]
]
];
// $result = $docker->createContainer($containerConfig);
// $docker->startContainer($result['Id']);
}
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
?>
三、常见问题排查清单
1. 证书生成问题
# 重新生成客户端证书
cd /etc/docker/tls
# 确保使用正确的 CN
openssl req -new -key client-key.pem -out client.csr \
-subj "/CN=client" \
-addext "keyUsage = digitalSignature, keyEncipherment" \
-addext "extendedKeyUsage = clientAuth"
openssl x509 -req -days 365 \
-in client.csr \
-CA ca.pem \
-CAkey ca-key.pem \
-out client-cert.pem \
-extfile <(printf "keyUsage=digitalSignature,keyEncipherment\nextendedKeyUsage=clientAuth")
2. Docker 服务端配置检查
# 检查 Docker 是否监听 2376
sudo netstat -tlnp | grep 2376
# 查看 Docker 日志
sudo journalctl -u docker --since "5 minutes ago"
# 验证 Docker 配置
sudo docker --tlsverify \
--tlscacert=ca.pem \
--tlscert=server-cert.pem \
--tlskey=server-key.pem \
-H=localhost:2376 version
3. PHP 环境检查
<?php
// 检查 PHP 扩展
echo "OpenSSL: " . (extension_loaded('openssl') ? 'Enabled' : 'Disabled') . "\n";
echo "cURL: " . (extension_loaded('curl') ? 'Enabled' : 'Disabled') . "\n";
// 检查 OpenSSL 版本
echo "OpenSSL Version: " . OPENSSL_VERSION_TEXT . "\n";
// 测试证书读取
$cert = file_get_contents('/path/to/certs/client-cert.pem');
echo "Certificate size: " . strlen($cert) . " bytes\n";
?>
四、快速解决方案
如果以上都太复杂,最直接的方法:
# 1. 重新生成所有证书
cd /etc/docker/tls
./generate-certs.sh # 如果有脚本
# 或使用上面的命令重新生成
# 2. 重启 Docker
sudo systemctl restart docker
# 3. 测试
curl --cert client-cert.pem --key client-key.pem --cacert ca.pem \
https://localhost:2376/version
如果仍然失败,检查 Docker 配置中的 tlsverify 是否设置为 true,这是强制要求客户端证书的关键设置。