Margrop
Articles264
Tags429
Categories23
1password AC ACP AI AP API AppDaemon Aqara CI/CD Caddy Cloudflare Cookie 认证 Cron D1 Date Diagrams.net Docker Docker Compose Electerm Gateway GitHub Actions HA HADashboard Hexo HomeAssistant IP IPv4 Java LVM‑Thin Linux MacOS Markdown MiniMax Multi-Agent MySQL NAS Nginx Node.js OpenAI OpenClaw OpenResty PPPoE Portainer PostgreSQL ProcessOn Prometheus Proxmox VE SOCKS5 SSL Session Shell Subagent TTS TimeMachine UML Uptime Kuma VPN VPS Web Windows Workers activate ad adb adblock agent aligenie aliyun alpine annotation aop authy autofs backup baidupan bash bitwarden boot brew browser caddy2 cdn centos cert certbot charles chat chrome classloader client clone closures cloudflare cmd command commit container crontab ctyun ddsm demo dependency deploy developer devtools dll dns docker domain download draw drawio dsm dump dylib edge exception export fail2ban feign firewall-cmd flow frp frpc frps fuckgfw function gcc gfw git github golang gperftools gridea grub gvt-g hacs havcs heap hello hexo hibernate hidpi hoisting homeassistant hosts html htmlparser https idea image img img2kvm import index install intel io ios ip iptables iptv ipv6 iso java javascript jetbrains jni jnilib jpa js json jsonb jupter jupyterlab jvm k8s kernel key kid kms kodi koolproxy koolproxyr kvm lan lastpass launchctl learning lede letsencrypt linux live low-code lvm lxc m3u8 mac macos mariadb markdown maven md5 microcode mirror modem modules monitor mount mstsc mysql n2n n5105 nas network nfs node node-red nodejs nohup notepad++ npm nssm ntp oop openfeign openssl os otp ovz p14 packet capture pat pdf pem perf ping pip plugin png powerbutton print pro proxy pve pvekclean python qcow2 qemu qemu-guest-agent rar reboot reflog remote remote desktop renew repo resize retina root route router rule rules runtime safari sata scipy-notebook scoping scp server slmgr so socks source spk spring springboot springfox ssh ssl stash string supernode svg svn swagger sync synology systemctl systemd tap tap-windows tapwindows telecom template terminal tls token totp tvbox txt ubuntu udisk ui undertow uninstall unlocker upgrade url v2ray vhd vim vlmcsd vm vmdk web websocket wechat windows with worker wow xiaoya xml yum zip 中国电信 云电脑 交换机 人机协作 代理 体检 健康检查 光猫 公网IP 内存 内网IP 写作 升级 协作 博客 反向代理 启动 夏令时 多智能体 多节点 多节点管理 天猫精灵 天翼云 安全 安装 定时任务 容器 容器网络 导入 小米 常用软件 广告屏蔽 序列号 应用市场 异常 感悟 打工 打工人 技术 抓包 排查 描述文件 故障 故障排查 效率 效率工具 无服务器 日记 时区 显卡虚拟化 智能家居 智能音箱 服务管理 架构 梯子 模块 流程 流程图 浏览器 漫游 激活 火绒 焦虑 玄学 生活 电信 画图 监控 直播源 端口扫描 管理 续期 网关 网络 网络风暴 群晖 脚本 腾讯 自动化 虚拟机 认证 证书 语雀 超时 路由 路由器 软件管家 软路由 运维 运维监控 通信机制 部署 配置 钉钉 镜像 镜像源 门窗传感器 问题排查 防火墙 阿里云 阿里源 集客

Hitokoto

Archive

记一次Docker容器内部网卡"假死"故障排查:从表象到根因

记一次Docker容器内部网卡"假死"故障排查:从表象到根因

记一次Docker容器内部网卡”假死”故障排查:从表象到根因

前言

Docker容器的网络问题,是运维工作中最常见也最让人头疼的问题之一。相比于虚拟机或物理机,容器的网络模型更加复杂——它涉及Docker的网络驱动(bridge、host、overlay、macvlan等)、宿主机的网络配置、容器内部的iptables规则,以及Linux内核的网络栈。任何一个环节出问题,都可能导致容器内部网络完全不可用。

今天我们要聊的,是一个特殊的问题:Docker容器的网卡”假死”——容器进程正常运行,从容器外部可以ping通容器的IP,但容器内部完全无法访问网络。

这种情况排查起来特别棘手,因为它的表象和真正的网络故障不同,需要用到特殊的排查方法。本文将详细记录一次完整的排查过程,供遇到类似问题的同学参考。

问题背景

业务场景

我们在某台服务器上运行了多个Docker容器,提供不同的服务:

  • openapi-svr容器:提供OpenAPI服务,监听端口8080
  • bot-svr容器:提供机器人服务,监听端口8081
  • postgres容器:提供PostgreSQL数据库,端口5432
  • redis容器:提供Redis缓存,端口6379

这些容器通过Docker bridge网络互通,容器之间通过服务名(如openapi-svrbot-svr)进行DNS解析和通信。

问题现象

某天早上,运维监控发现以下异常:

  1. 从宿主机可以正常访问容器内的服务

    • curl http://127.0.0.1:8080/api/health 返回正常
    • docker exec openapi-svr curl localhost:8080/api/health 也正常
  2. 但从容器内部访问其他容器的服务全部超时

    • docker exec openapi-svr curl http://bot-svr:8081/api/health 超时
    • docker exec openapi-svr ping bot-svr 不通
    • docker exec openapi-svr ping 8.8.8.8 也不通
  3. 容器的进程和端口都正常

    • docker ps 显示所有容器状态为Up
    • docker exec openapi-svr netstat -tlnp 显示服务端口在监听
  4. 重启容器可以临时解决问题,但过几天又会复发

环境信息

项目
Docker 版本 24.0.7
容器网络模式 bridge(默认)
宿主机系统 Ubuntu 22.04 LTS
问题容器数量 多个(使用bridge网络的容器)
复发频率 约每周1-2次
临时解决方案 重启容器

问题分析

初步分析:为什么是”假死”?

从问题现象来看,这是一个非常典型的”网卡假死”问题:

  • 容器进程正常:服务进程还在运行,端口还在监听
  • 容器外部访问正常:从宿主机可以正常访问
  • 容器内部访问异常:从容器内部无法访问任何网络地址

这种情况通常意味着:容器的网络接口(通常是eth0)虽然在容器命名空间中存在,但已经失去了网络通信能力。

这就好像你的电脑网卡灯还亮着,操作系统也显示网卡已连接,但实际上网络已经不通了。

可能的原因

  1. Docker bridge网络的问题:Docker的bridge网络通过veth pair(虚拟以太网对)连接容器和宿主机。如果veth pair的一端出现问题,可能导致容器内部网络中断。

  2. iptables规则问题:Docker会自动在宿主机上配置iptables规则,进行NAT和包过滤。如果这些规则被意外修改或损坏,可能导致容器网络中断。

  3. 内核网络栈问题:Linux内核的网络栈在某些情况下可能出现异常,比如内存泄漏导致的网络缓冲区满,或者某些corner case的bug。

  4. 容器资源限制问题:如果容器达到了某些资源限制(如网络连接数限制),可能导致网络不可用。

  5. DNS解析问题:虽然DNS服务器本身是正常的,但如果容器的DNS配置丢失或损坏,也可能导致所有基于服务名的访问失败。

排查过程

第一步:确认问题的范围和现象

首先,在宿主机上确认问题的范围:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看所有运行中的容器
docker ps

# 查看容器网络状态
docker network ls
docker network inspect bridge

# 测试从宿主机访问容器
curl http://127.0.0.1:8080/api/health
curl http://127.0.0.1:8081/api/health

# 测试容器之间的连通性(使用docker exec)
docker exec openapi-svr ping -c 3 8.8.8.8
docker exec openapi-svr ping -c 3 bot-svr
docker exec exec openapi-svr ping -c 3 172.17.0.2

观察到

  • 从宿主机访问容器服务:正常
  • 从容器内ping 8.8.8.8:不通
  • 从容器内ping容器IP:不通
  • 从容器内ping服务名:不通

这说明问题范围很广,不仅仅是DNS问题。

第二步:检查容器的网络接口

使用docker exec进入容器,检查容器内部的网络配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 进入容器
docker exec -it openapi-svr /bin/bash

# 查看网络接口
ip addr show

# 查看路由表
ip route show

# 查看DNS配置
cat /etc/resolv.conf

# 查看网络接口统计(是否有大量错误或丢包)
ip -s link show eth0

# 查看ARP表
arp -a

观察到

  • eth0接口存在,状态是UP
  • IP地址配置正确(如172.17.0.2)
  • 路由表有默认网关
  • DNS配置指向了正确的DNS服务器(通常是Docker内置的127.0.0.11)
  • ip -s link show eth0显示有大量RX errorsdropped

这就是关键线索了!网卡的接收(RX)有大量错误和丢包,说明数据包根本没有正确到达应用层。

第三步:检查Docker bridge网络的状态

回到宿主机,检查Docker bridge网络的状态:

1
2
3
4
5
6
7
8
9
10
11
12
# 查看docker0网桥
ip link show docker0
ip addr show docker0

# 查看连接到这个网桥的veth设备
bridge link show

# 或者用brctl(需要安装bridge-utils)
brctl show

# 查看网桥上的STP状态
bridge link show | grep -i stp

观察到:docker0网桥状态正常,所有veth设备都连接正常。

第四步:检查iptables规则

Docker会自动配置iptables规则,检查这些规则是否正常:

1
2
3
4
5
6
7
8
9
10
11
# 查看Docker的NAT规则
iptables -t nat -L -n -v | head -50

# 查看Filter规则
iptables -L -n -v | head -50

# 检查FORWARD链
iptables -L FORWARD -n -v

# 检查是否有DROP或REJECT规则
iptables -L -n | grep -i drop

观察到:iptables规则看起来正常,没有异常的DROP规则。

第五步:检查内核网络缓冲区

网络丢包和错误可能与内核缓冲区有关:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看网络缓冲区(netstat)
netstat -s | grep -i error
netstat -s | grep -i drop

# 查看详细的网卡统计
cat /proc/net/dev

# 查看内存缓冲区
cat /proc/sys/net/core/rmem_max
cat /proc/sys/net/core/wmem_max
cat /proc/sys/net/core/netdev_max_backlog

# 查看socket缓冲区
ss -s

观察到:部分网卡的错误计数较高,但还不足以解释完全无法通信的问题。

第六步:尝试修复容器网络

在找到根因之前,可以尝试一些修复方法:

方法一:重启容器(最简单有效)

1
2
3
4
5
6
# 停止并重启容器
docker restart openapi-svr

# 或者先stop再start
docker stop openapi-svr
docker start openapi-svr

注意:重启容器会导致IP地址变化(除非使用固定IP或网络别名)。

方法二:断开并重新连接网络

1
2
3
4
5
6
7
8
9
# 断开容器的网络连接
docker network disconnect bridge openapi-svr

# 重新连接
docker network connect bridge openapi-svr

# 或者强制重新连接
docker network disconnect -f bridge openapi-svr
docker network connect bridge openapi-svr

方法三:重建整个Docker网络

1
2
3
4
5
6
7
8
9
10
11
# 查看当前网络配置
docker network inspect bridge

# 备份配置
cp -r /var/lib/docker/network /var/lib/docker/network.bak

# 重启Docker服务(会重建所有网络)
systemctl restart docker

# 验证
docker network inspect bridge

观察到:方法一(重启容器)可以临时解决问题,但过几天又复发。

第七步:深入分析根因

经过多轮排查,我们发现了一些规律:

  1. 问题通常发生在容器运行较长时间后
  2. 重启Docker服务可以更长时间地解决问题
  3. 多个容器同时出现问题,而不是单个容器

这暗示问题可能在Docker daemon层面,或者是宿主机的网络配置层面。

检查Docker daemon的日志:

1
2
3
4
5
6
# 查看Docker daemon日志
journalctl -u docker -n 200 --since "1 hour ago"

# 查看Docker的debug日志
grep -i error /var/log/docker.log
grep -i warn /var/log/docker.log

发现了以下关键日志:

1
2
3
level=info msg="libcontainerd: started new containerd-shim process"
level=warn msg="fatal: bridge network device didn't geteth0"
level=error msg="network namespace cleanup failed"

这条”bridge network device didn’t geteth0”日志非常关键!它说明在某些情况下,容器启动时没有正确获取到eth0网络接口。

进一步检查宿主机的dmesg:

1
2
dmesg | grep -i "veth"
dmesg | grep -i "docker"

发现了一些网络设备的异常日志。

根因分析

直接原因

Docker容器在启动或运行过程中,veth pair(虚拟以太网对)的容器端(eth0)出现了异常,导致容器虽然能看到eth0接口(状态UP),但实际上网络通信已经完全中断。

深层原因

  1. Docker daemon在处理大量容器网络操作时,存在竞态条件(race condition)

    • 当容器频繁启动、停止、或重新连接网络时,veth pair的创建和销毁可能不同步
    • 这种竞态条件在容器数量多、负载高的环境下更容易触发
  2. 宿主机内核网络栈在高并发场景下可能出现内存问题

    • 网络缓冲区的内存分配可能失败或泄漏
    • 导致某些数据包无法正常处理
  3. Docker版本可能存在已知bug

    • Docker 24.0.7在某些场景下有网络相关的bug报告
    • 建议升级到最新稳定版本

解决方案

紧急修复:临时恢复容器网络

如果容器已经出现”假死”,可以尝试以下方法临时恢复:

方法一:重启单个容器

1
docker restart <container_name>

方法二:断开并重连容器网络

1
2
docker network disconnect -f bridge <container_name>
docker network connect bridge <container_name>

方法三:重启Docker服务(最彻底)

1
2
3
4
5
6
7
8
9
10
11
# 保存当前运行的容器状态
docker ps > /tmp/docker_ps_backup.txt

# 重启Docker daemon
systemctl restart docker

# 等待服务恢复
sleep 10

# 验证容器状态
docker ps

长期方案:防止问题再次发生

方案一:升级Docker到最新版本

1
2
3
4
5
6
7
8
9
10
11
12
# 查看当前版本
docker --version

# 升级Docker(Ubuntu)
apt update
apt upgrade docker.io

# 或者使用官方脚本安装最新版本
curl -fsSL https://get.docker.com | sh

# 重启Docker
systemctl restart docker

方案二:使用Docker的健康检查和自动恢复

在Docker Compose配置中添加健康检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
services:
openapi-svr:
image: openapi-svr:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
networks:
- backend

networks:
backend:
driver: bridge
enable_ipv6: false

这样,当健康检查连续失败3次后,Docker会自动重启容器。

方案三:使用监控脚本自动检测和修复

创建一个监控脚本,自动检测并修复网络问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/bin/bash
# docker-network-monitor.sh

CONTAINERS=("openapi-svr" "bot-svr" "postgres" "redis")
LOG_FILE="/var/log/docker-network-monitor.log"

log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> $LOG_FILE
}

check_container_network() {
local container=$1
# 检查容器网络连通性
if docker exec $container ping -c 1 -W 2 8.8.8.8 > /dev/null 2>&1; then
return 0
else
return 1
fi
}

for container in "${CONTAINERS[@]}"; do
if ! check_container_network $container; then
log "WARNING: $container network is down, attempting to fix..."

# 尝试断开并重连网络
docker network disconnect -f bridge $container
sleep 2
docker network connect bridge $container

# 再次检查
sleep 5
if check_container_network $container; then
log "SUCCESS: $container network recovered"
else
log "ERROR: Failed to recover $container, restarting container..."
docker restart $container
fi
else
log "OK: $container network is healthy"
fi
done

添加到cron定时任务(每5分钟检查一次):

1
2
3
crontab -e
# 添加以下行
*/5 * * * * /path/to/docker-network-monitor.sh >> /var/log/docker-network-monitor-cron.log 2>&1

方案四:配置更高的内核网络参数

调整Linux内核的网络参数,减少网络问题的发生:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 编辑sysctl配置
cat >> /etc/sysctl.conf << EOF
# Docker网络优化
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.netdev_max_backlog = 5000
net.core.somaxconn = 1024
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_max_syn_backlog = 8096
EOF

# 应用配置
sysctl -p

方案五:定期清理Docker网络资源

定期清理无用的网络资源,防止资源泄漏:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash
# docker-network-cleanup.sh

# 清理未使用的网络
docker network prune -f

# 清理无用的veth设备
ip link show | grep veth | while read line; do
veth=$(echo $line | awk -F@ '{print $1}' | awk '{print $2}')
if [ -n "$veth" ]; then
if ! ip link show $veth > /dev/null 2>&1; then
echo "Found orphaned veth: $veth"
fi
fi
done

# 记录Docker网络状态
docker network inspect bridge > /tmp/docker_bridge_$(date +%Y%m%d).json

添加到cron(每天凌晨执行):

1
2
3
crontab -e
# 添加
0 3 * * * /path/to/docker-network-cleanup.sh >> /var/log/docker-cleanup.log 2>&1

一键排查脚本

如果你遇到了类似的Docker容器网络问题,可以使用以下脚本进行快速排查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/bin/bash
# docker-network-debug.sh

echo "=== Docker 容器网络问题排查脚本 ==="
echo ""

# 1. 检查Docker服务状态
echo "[1/7] 检查Docker服务状态..."
systemctl is-active docker && echo "✅ Docker 服务运行正常" || echo "❌ Docker 服务未运行"
docker --version
echo ""

# 2. 检查容器运行状态
echo "[2/7] 检查容器运行状态..."
RUNNING=$(docker ps -q | wc -l)
TOTAL=$(docker ps -aq | wc -l)
echo " 运行中容器: $RUNNING / $TOTAL"
echo ""

# 3. 检查Docker网络
echo "[3/7] 检查Docker网络..."
docker network ls
echo ""

# 4. 检查bridge网络详情
echo "[4/7] 检查bridge网络详情..."
docker network inspect bridge | jq '.[0].IPAM.Config[0]' 2>/dev/null || echo " 无法获取bridge网络信息"
echo ""

# 5. 检查容器网络连通性
echo "[5/7] 检查容器网络连通性..."
for container in $(docker ps -q); do
name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
if docker exec $container ping -c 1 -W 2 8.8.8.8 > /dev/null 2>&1; then
echo " ✅ $name: 网络正常"
else
echo " ❌ $name: 网络异常"
fi
done
echo ""

# 6. 检查容器内部网络接口
echo "[6/7] 检查容器内部网络接口..."
for container in $(docker ps -q | head -3); do
name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
echo " === $name ==="
docker exec $container ip -s link show eth0 2>/dev/null | head -5 || echo " 无法获取网络接口信息"
done
echo ""

# 7. 检查iptables规则
echo "[7/7] 检查iptables规则..."
FORWARD=$(iptables -L FORWARD -n -v | grep -c "DOCKER" || echo "0")
echo " Docker FORWARD链规则数: $FORWARD"
echo ""

echo "=== 排查完成 ==="
echo ""
echo "如果发现问题,建议尝试以下方法:"
echo "1. 重启容器: docker restart <container_name>"
echo "2. 重启Docker服务: systemctl restart docker"
echo "3. 检查Docker日志: journalctl -u docker -n 50"
echo "4. 检查内核日志: dmesg | grep -i docker"

常见问题解答

Q1:Docker容器的网络和宿主机的网络有什么关系?

A:Docker容器默认使用bridge网络模式。在这种模式下:

  • Docker会在宿主机上创建一个名为docker0的虚拟网桥
  • 每个容器会有一对veth(虚拟以太网对),一端在容器内(通常是eth0),另一端连接在docker0网桥上
  • 容器通过docker0网桥与宿主机和其他容器通信
  • 宿主机通过NAT让容器访问外部网络

Q2:为什么从宿主机可以访问容器,但从容器内部不能访问外部网络?

A:这通常说明:

  • docker0网桥本身是正常的
  • 问题出在容器的eth0接口或veth pair的容器端
  • 可能是容器内部的网络栈出现了异常

Q3:重启容器会改变IP地址吗?

A:默认情况下,Docker会为容器分配动态IP地址。重启后IP地址可能会变化。如果需要固定IP,可以使用--ip参数或创建自定义网络。

Q4:如何防止容器网络假死后无人发现?

A:建议配置以下监控:

  1. 容器健康检查(healthcheck)
  2. 定期从容器内部测试网络连通性
  3. 监控veth pair的状态
  4. 监控docker0网桥的流量和错误统计

Q5:Docker的bridge、host、overlay网络模式有什么区别?

A:

  • bridge:默认模式,容器在自己的网络命名空间中,通过NAT与外部通信
  • host:容器直接使用宿主机的网络命名空间,没有网络隔离
  • overlay:用于Docker Swarm多个主机之间的容器通信
  • macvlan:让容器拥有独立的MAC地址,看起来像物理机

Q6:如何查看某个容器的veth pair在宿主机上对应的接口?

A:

1
2
3
4
5
6
7
8
9
10
11
# 获取容器的PID
PID=$(docker inspect --format='{{.State.Pid}}' <container_name>)

# 查看宿主机上对应的veth设备
ls -la /proc/$PID/ns/net

# 或者使用ip命令
ip link show | grep veth

# 通过容器ID找到对应的veth
docker inspect --format='{{range .NetworkSettings.Networks}}{{.MacAddress}}{{end}}' <container_name>

经验总结

排查技巧

  1. 从外到内,分层排查:先从宿主机开始,逐步深入到容器内部
  2. 关注网络接口统计ip -s link show可以显示网卡的错误和丢包统计
  3. 查看日志是关键:Docker daemon日志、dmesg、内核日志往往包含关键线索
  4. 记录复现步骤:问题是否稳定复现,对诊断很重要

预防措施

  1. 配置健康检查:使用Docker的healthcheck指令,让Docker自动检测并恢复异常容器
  2. 监控网络指标:定期检查docker0网桥和veth设备的统计信息
  3. 定期清理资源:清理无用的网络、容器和镜像,防止资源泄漏
  4. 升级到稳定版本:使用Docker最新稳定版本,避免已知bug

运维建议

  1. **不要依赖”重启大法”**:虽然重启能临时解决问题,但找到根因才能彻底修复
  2. 做好日志收集:Docker日志、内核日志、系统日志都应该收集和分析
  3. 理解网络模型:深入理解Docker的网络模型,才能更好地排查问题
  4. 自动化监控和恢复:将重复性的检查和修复工作自动化,减少人工干预

延伸阅读

结语

Docker容器网络”假死”的问题,排查起来确实比较棘手,因为它涉及多个层面的网络组件。但只要掌握了正确的排查方法,从表象到根因,一步步分析,就能找到问题的真正原因。

本文记录的排查过程,希望能为遇到类似问题的同学提供一些参考。记住,排查网络问题最重要的是分层分析日志追踪——先确认问题的范围,再逐层深入,最后找到根因。

当然,最重要的还是预防胜于治疗。做好健康检查、配置监控告警、定期清理资源,这些日常工作虽然繁琐,但在关键时刻能救你一命。

祝你永无网络问题!


作者:小六,一个今天终于搞清楚Docker容器网络假死问题根因的技术人

Author:Margrop
Link:http://blog.margrop.com/post/2026-04-13-docker-container-network-card-death-troubleshooting/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可