在企业的生产环境中,Docker已经成为应用部署的标准配置。随着业务规模的扩大,我们往往需要在多台服务器上部署Docker容器,这就带来了一个很现实的问题:如何安全地管理这些分布在各个地方的Docker主机?
想象一下,如果你直接开放Docker的2375端口,就像把自家大门的钥匙插在门锁上一样危险。任何知道这个端口的人都可以对你的Docker环境为所欲为,包括创建、删除容器,甚至获取宿主机的root权限。我就曾经遇到过因为没做好安全防护,导致测试环境的Docker被入侵,最后不得不重装系统的惨痛经历。
Portainer作为一款轻量级的Docker管理工具,不仅提供了友好的Web界面,更重要的是它能帮助我们集中管理多个Docker环境。但在这之前,我们必须先解决安全问题。TLS证书就像是一把特殊的钥匙,只有持有正确证书的人才能访问Docker API,这就从根本上杜绝了未授权访问的风险。
在开始生成证书之前,我们需要确保服务器上已经安装了OpenSSL工具。可以通过以下命令检查:
bash复制openssl version
如果没有安装,在基于RPM的系统(如CentOS)上可以执行:
bash复制yum install openssl -y
在基于Debian的系统(如Ubuntu)上则是:
bash复制apt-get install openssl -y
接下来,我们需要创建一个专门的目录来存放证书文件。我习惯放在/etc/docker/certs目录下:
bash复制mkdir -p /etc/docker/certs && cd /etc/docker/certs
手动生成TLS证书需要执行很多步骤,容易出错。我推荐使用自动化脚本来完成这个工作。下面是我在实际项目中使用的改进版脚本:
bash复制#!/bin/bash
# 设置证书密码,建议使用复杂密码
CERT_PASSWORD="YourStrongPassword123!"
# 设置证书信息
COUNTRY="CN"
STATE="YourState"
CITY="YourCity"
ORGANIZATION="YourCompany"
ORGANIZATIONAL_UNIT="IT"
COMMON_NAME=$(hostname)
EMAIL="admin@yourcompany.com"
# 生成CA密钥
openssl genrsa -aes256 -passout pass:${CERT_PASSWORD} -out ca-key.pem 4096
# 生成CA证书
openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem -passin pass:${CERT_PASSWORD} -subj "/C=${COUNTRY}/ST=${STATE}/L=${CITY}/O=${ORGANIZATION}/OU=${ORGANIZATIONAL_UNIT}/CN=${COMMON_NAME}/emailAddress=${EMAIL}"
# 生成服务器密钥
openssl genrsa -out server-key.pem 4096
# 生成服务器证书签名请求
openssl req -subj "/CN=${COMMON_NAME}" -sha256 -new -key server-key.pem -out server.csr
# 配置证书扩展属性
echo "subjectAltName = DNS:${COMMON_NAME},IP:127.0.0.1,IP:$(hostname -I | awk '{print $1}')" > extfile.cnf
echo "extendedKeyUsage = serverAuth" >> extfile.cnf
# 生成服务器证书
openssl x509 -req -days 365 -sha256 -in server.csr -passin pass:${CERT_PASSWORD} -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile extfile.cnf
# 生成客户端密钥
openssl genrsa -out key.pem 4096
# 生成客户端证书签名请求
openssl req -subj '/CN=client' -new -key key.pem -out client.csr
# 配置客户端证书扩展属性
echo "extendedKeyUsage = clientAuth" > extfile-client.cnf
# 生成客户端证书
openssl x509 -req -days 365 -sha256 -in client.csr -passin pass:${CERT_PASSWORD} -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile extfile-client.cnf
# 清理临时文件
rm -f client.csr server.csr ca.srl extfile.cnf extfile-client.cnf
# 设置文件权限
chmod -v 0400 ca-key.pem key.pem server-key.pem
chmod -v 0444 ca.pem server-cert.pem cert.pem
echo "证书生成完成!"
echo "客户端需要文件: key.pem, cert.pem, ca.pem"
echo "服务器端需要文件: ca.pem, server-cert.pem, server-key.pem"
将上述内容保存为gen-tls.sh文件后,赋予执行权限并运行:
bash复制chmod +x gen-tls.sh
./gen-tls.sh
脚本执行完成后,会在当前目录生成以下文件:
有了TLS证书后,我们需要配置Docker守护进程使用这些证书。首先备份原有的Docker服务配置文件:
bash复制cp /usr/lib/systemd/system/docker.service /usr/lib/systemd/system/docker.service.bak
然后编辑配置文件:
bash复制vim /usr/lib/systemd/system/docker.service
找到ExecStart行,修改为以下内容(假设证书存放在/etc/docker/certs目录):
bash复制ExecStart=/usr/bin/dockerd \
-H fd:// \
--containerd=/run/containerd/containerd.sock \
--tlsverify \
--tlscacert=/etc/docker/certs/ca.pem \
--tlscert=/etc/docker/certs/server-cert.pem \
--tlskey=/etc/docker/certs/server-key.pem \
-H tcp://0.0.0.0:2376
这里有几个关键点需要注意:
修改完成后,重新加载systemd配置并重启Docker服务:
bash复制systemctl daemon-reload
systemctl restart docker
为了确认TLS配置已经生效,我们可以尝试从另一台机器连接:
bash复制docker --tlsverify \
--tlscacert=ca.pem \
--tlscert=cert.pem \
--tlskey=key.pem \
-H=tcp://your-server-ip:2376 \
version
如果配置正确,你应该能看到Docker版本信息。如果出现错误,可以检查Docker日志:
bash复制journalctl -u docker.service -f
现在我们已经有了安全的Docker API,接下来就可以部署Portainer来管理这些Docker主机了。我推荐使用docker-compose方式来部署:
bash复制version: '3'
services:
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: always
ports:
- "9000:9000"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
volumes:
portainer_data:
保存为docker-compose.yml后,执行:
bash复制docker-compose up -d
Portainer启动后,访问http://your-server-ip:9000完成初始设置。然后按照以下步骤添加远程Docker环境:
如果一切正常,Portainer会显示连接成功。你现在可以在一个统一的界面中管理多个Docker主机了。
对于Docker Swarm集群,添加方式类似但有几点不同:
Portainer可以显示Swarm集群的整体状态,包括节点数量、服务状态等。你还可以通过它来部署新的服务到Swarm集群中。
问题1:连接时出现"x509: certificate signed by unknown authority"错误
这通常意味着客户端使用的CA证书与服务器端不匹配。检查以下几点:
问题2:Portainer无法连接到远程Docker
首先检查网络连通性:
bash复制telnet remote-docker-ip 2376
如果网络正常,检查Docker日志:
bash复制journalctl -u docker.service -f
常见原因包括:
问题3:证书即将过期
可以通过以下命令检查证书有效期:
bash复制openssl x509 -in server-cert.pem -noout -dates
建议在证书过期前一个月就开始准备更新流程,避免服务中断。