如何解决Docker环境中S3.Client.upload_fileobj触发的SSL证书验证错误?
解决Docker中boto3连接S3的SSL证书验证失败问题
问题场景
在指定--platform=linux/amd64的Docker镜像中运行Python代码,通过boto3上传JSON文件到S3时,触发ssl.SSLCertVerificationError错误,提示无法获取本地颁发者证书。相同代码在本地开发环境可正常运行,已尝试安装certifi、openssl,设置SSL_CERT_FILE和REQUESTS_CA_BUNDLE环境变量,更换多个Python基础镜像,但问题仍未解决。
错误日志
ssl_sock = ssl_wrap_socket( File "/root/.cache/pypoetry/virtualenvs/gg-9TtSrW0h-py3.10/lib/python3.10/site-packages/urllib3/util/ssl_.py", line 469, in ssl_wrap_socket ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls, server_hostname) File "/root/.cache/pypoetry/virtualenvs/gg-9TtSrW0h-py3.10/lib/python3.10/site-packages/urllib3/util/ssl_.py", line 513, in _ssl_wrap_socket_impl return ssl_context.wrap_socket(sock, server_hostname=server_hostname) File "/usr/local/lib/python3.10/ssl.py", line 513, in wrap_socket return self.sslsocket_class._create( File "/usr/local/lib/python3.10/ssl.py", line 1104, in _create self.do_handshake() File "/usr/local/lib/python3.10/ssl.py", line 1375, in do_handshake self._sslobj.do_handshake() ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1007)
相关代码
S3连接类:
class S3Connection(Connection): def __enter__(self): self._open() return self def __exit__( self, exception_type: str, exception_value: str, exception_traceback: str, ): self._close() def _open(self): self.sts_client = get_sts_client() self.credentials_response = self.sts_client.assume_role( RoleArn=HIPPOGRIFF_ROLE, RoleSessionName=self.session_name, ) self.session = get_session( self.credentials_response['Credentials']['AccessKeyId'], self.credentials_response['Credentials']['SecretAccessKey'], self.credentials_response['Credentials']['SessionToken'], ) self.s3_client = get_s3_client(session=self.session) def _close(self): self.sts_client.close() self.s3_client.close()
S3上传函数:
def land_to_aws_s3(response, table_name, file_name): logger.info( f'{LOG_ENV_PREFIX}: \ Landing json file into s3 bucket ' + 'table_name: \n:' + table_name, ) json_bytes = BytesIO(json.dumps(response.json()['data']).encode('utf-8')) with S3Connection() as s3_connection: s3_response = s3_connection.s3_client.upload_fileobj( json_bytes, Bucket=LANDING_BUCKET, Key=LANDING_BUCKET_PATH + '/'+table_name+'/'+file_name, ExtraArgs={'ContentType': 'application/json'}, ) return s3_response
问题原因
- Docker镜像CA证书链不完整:多数轻量Python基础镜像(如
python:alpine)默认未预装完整的系统级根CA证书,即使安装了certifi,系统证书与Python使用的证书链可能不同步,缺失中间颁发者证书。 - 环境变量未正确生效:虽然设置了
SSL_CERT_FILE和REQUESTS_CA_BUNDLE,但boto3依赖的urllib3可能未正确读取这些变量,或环境变量设置时机错误(如虚拟环境启动后才设置,导致Python进程未加载)。 - 虚拟环境证书隔离:Poetry创建的虚拟环境可能未继承系统证书配置,certifi的证书路径在虚拟环境中未正确关联。
- 内部代理/私有CA问题:若环境使用内部代理或私有CA,未将对应的CA证书添加到系统证书库中,导致无法验证S3服务器证书。
修复建议
1. 安装系统级根CA证书
根据镜像的操作系统类型,在Dockerfile中添加证书安装命令:
- Debian/Ubuntu系镜像:
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && update-ca-certificates
- Alpine系镜像:
RUN apk add --no-cache ca-certificates && update-ca-certificates
2. 确保环境变量正确设置
在Dockerfile中提前设置环境变量,确保Python进程启动时能读取到:
ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt ENV REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
也可在启动容器时通过-e参数传递,避免镜像构建时的路径冲突。
3. 显式配置boto3客户端的SSL上下文
在代码中强制指定CA证书路径,不依赖环境变量:
import ssl import boto3 from botocore.client import Config def get_sts_client(): # 创建自定义SSL上下文,指定系统证书文件路径 ssl_context = ssl.create_default_context(cafile='/etc/ssl/certs/ca-certificates.crt') config = Config(ssl_context=ssl_context) return boto3.client('sts', config=config) def get_s3_client(session=None): ssl_context = ssl.create_default_context(cafile='/etc/ssl/certs/ca-certificates.crt') config = Config(ssl_context=ssl_context) if session: return session.client('s3', config=config) return boto3.client('s3', config=config)
若使用certifi的证书文件,可替换为cafile=certifi.where()。
4. 添加内部CA证书(如有)
如果环境使用私有CA,需将CA证书添加到系统证书库:
# 复制内部CA证书到指定目录 COPY internal-ca.crt /usr/local/share/ca-certificates/ # 更新证书链 RUN update-ca-certificates
5. 验证证书路径正确性
在Docker镜像中执行以下命令,确认证书文件存在且可读:
ls -l /etc/ssl/certs/ca-certificates.crt python -c "import ssl; print(ssl.get_default_verify_paths())" python -c "import certifi; print(certifi.where())"
内容的提问来源于stack exchange,提问作者Ali Sakanak
相关产品推荐
相关产品推荐

