土法炼钢兴趣小组的算法知识备份

【开源许可与版权工程】Copyleft 的工程边界:动态链接、容器、SaaS 算不算「分发」

文章导航

分类入口
architectureopensource
标签入口
#copyleft#gpl#lgpl#agpl#distribution#docker#saas#linking#container#webpack

目录

在上一篇开源许可证全景:宽松、弱 Copyleft、强 Copyleft、网络 Copyleft中,我们按「传染性强弱」把常见许可证分成了四类。工程师真正头疼的不是分类,而是判定:我这个系统到底有没有触发 Copyleft?

这些问题的答案,写在 GPLv2 的「distribute」、GPLv3 的「convey」、LGPL-2.1 的§4/§5、AGPL-3.0 的§13 这些条款里。本文把它们拆成可操作的工程判定规则,并用一张触发矩阵作为索引。

Copyleft 触发场景矩阵

一、重新理解「分发」:从 distribute 到 convey

Copyleft 的全部义务,都挂在「触发事件」上。没有触发事件,GPL 就只是一份「你自己可以自由使用」的许可证,并不要求你做任何额外的事情。

1.1 GPLv2 的 distribute:物理传递的隐喻

GPL-2.0 通篇使用的动词是 distribute(分发)。GPLv2 §1 规定:

You may copy and distribute verbatim copies of the Program’s source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty…

以及 §3:

You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following…

这里的 distribute 继承的是 1991 年美国版权法(Copyright Act)里的「distribution right」概念:把作品的一份拷贝从一方转移到另一方,物理介质的隐喻非常明显——软盘、磁带、CD-ROM。

在这个语境下,内部使用(internal use)——哪怕是一个一万人的公司、服务器部署在一万台机器上——只要软件没有「离开公司的控制范围」,就不算 distribute,因此也不触发 GPLv2 的源代码披露义务。这一点在 FSF 的 GPL FAQ「Is making and using multiple copies within one organization or company ‘distribution’?」中有明确解释:同一法人实体内部的拷贝不构成分发。

1.2 GPLv3 的 convey:意图更宽泛的新动词

2007 年的 GPL-3.0 引入了一个新动词 convey(传递)。这并非同义替换,而是一次有意的语义升级。GPLv3 §0 给出了定义:

To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.

然后 §6「Conveying Non-Source Forms」专门规定以目标代码(object code)形式传递时的义务,包括:

convey 比 distribute 更宽泛的地方在于:它覆盖了 propagation(传播)的更多形态,包括「让第三方能够制作或接收副本的任何行为」。但 GPLv3 明确把「仅通过计算机网络与用户交互、没有副本转移」排除在外——这就是后来 AGPL-3.0 §13 要堵的「SaaS 漏洞」。

1.3 判定的黄金标准:软件是否离开了你的控制范围

抛开具体条款,工程师需要记住一条朴素的判断原则:

软件是否离开了你(你的法人实体)的控制范围,到达了另一个法人或自然人手里?

这条原则不能覆盖所有边缘情况(比如控股子公司之间的传递、合资公司、外包开发),但足以处理 90% 的日常工程决策。

1.4 子组件:关联公司、承包商与云账号

FSF 在 GPL FAQ 里补充了两类常见的「灰色边界」:

  1. 承包商(contractor):如果你雇佣一个外部开发者,为你修改 GPL 代码,然后只把结果交还给你,FSF 认为这不构成「对外分发」——因为承包商是作为你的代理在工作。一旦承包商自己把代码给了第三方,那就是分发。
  2. 关联公司:同一母公司下的不同法人实体之间,严格意义上是不同的「party」。FSF 的立场较为宽松,认为通常不算分发;但若被收购或分立,代码跟随实体变更时可能构成分发事件。

在云原生场景,云账号的归属同样重要:如果用户登录自己的 AWS 账号运行你的镜像,那是他在「自我托管」;如果你在自己的账号里替用户跑镜像并把结果返回给他,那就接近 SaaS 语义,AGPL 立刻开始说话。


二、动态链接 vs 静态链接:LGPL 的精巧设计

Copyleft 有一个从立项之初就存在的争议:把一个 GPL 库链接进我的专有程序,我的程序是否变成「衍生作品(derivative work)」,从而必须整体开源?

Richard Stallman 和 FSF 的观点是:。链接(无论静态还是动态)在运行时把两份代码合并成一个地址空间、共享内存、共享调用栈,法律上构成「combined work(组合作品)」——除非有明确的许可证例外。

但这显然过于激进。如果连 printf 都不能调用,GPL 库就没法被专有软件使用,开源生态的扩散就会受阻。于是 FSF 专门写了 LGPL(Lesser GPL,早期叫 Library GPL)来提供链接例外。

2.1 LGPL-2.1 的链接例外:§4、§5、§6

LGPL-2.1 是目前仍广泛使用的版本,它的核心在§5 和§6。

§5 规定:「使用但不包含(使用通过头文件、不包含实际代码)库中任何部分的作品」不是 derivative work,因此不受 LGPL 覆盖:

A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a “work that uses the Library”. Such a work, in isolation, is not a derivative work of the Library…

§6 则规定,你可以将你的程序与 LGPL 库「组合或链接」,并以你选择的条款分发组合作品,但必须允许接收者修改并替换 LGPL 库

As an exception to the Sections above, you may also combine or link a “work that uses the Library” with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer’s own use and reverse engineering for debugging such modifications.

具体的「允许替换」包括以下形式之一:

这就是为什么动态链接 LGPL 库在工程上被视为安全——它天然满足§6(b)。静态链接也可以合规,但你必须额外提供目标文件(.o)或可重链接的形式。

2.2 LGPL-3.0:基于 GPL-3.0 的「薄层加法」

LGPL-3.0(2007)不再是一份独立的长文档,而是一份简短的「附加许可」,挂在 GPL-3.0 之上。它的核心是§4「Combined Works」:允许你把「Combined Work」——一个使用了 LGPL 库的程序——以你选择的条款分发,前提同样是保留用户替换库的能力(§4(d)(0) 动态链接或 §4(d)(1) 提供链接所需的目标文件)。

工程上的判断和 LGPL-2.1 基本一致:动态链接 → 安全;静态链接 → 需要额外提供目标文件

2.3 Java 的 Classpath Exception:为什么需要它

OpenJDK 的许可证是「GPL-2.0 with Classpath Exception」。Classpath Exception 的原文简短但关键:

Linking this library statically or dynamically with other modules is making a combined work based on this library. Thus, the terms and conditions of the GNU General Public License cover the whole combination. As a special exception, the copyright holders of this library give you permission to link this library with independent modules to produce an executable, regardless of the license terms of these independent modules…

为什么要加这个例外?因为 Java 的标准类库和运行时是紧耦合的:任何一个 Java 程序都会隐式地 import java.lang.*,并运行在 JVM 上。如果没有 Classpath Exception,所有在 OpenJDK 上运行的 Java 应用都会变成 GPL 的 combined work,Java 生态将无法容纳任何专有软件。

GNU Classpath 项目(后来被 OpenJDK 大量吸收)就是在这个背景下设计了这条例外,它随 Sun 开源 Java 时一并进入 OpenJDK。

2.4 工程判断的速查表

链接关系 许可证组合 触发情况 必须做什么
静态链接 专有 + GPL-2.0 触发整体 GPL 整个程序必须 GPL 发布
动态链接 专有 + GPL-2.0 FSF 认为触发 整个程序必须 GPL 发布(争议)
静态链接 专有 + LGPL-2.1 部分触发 提供目标文件允许重链接
动态链接 专有 + LGPL-2.1 部分触发 保留用户替换库的能力
静态链接 专有 + LGPL-3.0 部分触发 提供链接目标或源码
动态链接 专有 + LGPL-3.0 部分触发 满足§4(d)(0)
链接 专有 + OpenJDK(Classpath) 不触发 无额外义务
链接 专有 + MPL-2.0 库 文件级触发 修改过的 MPL 文件保持 MPL

注意:关于「动态链接是否算 derivative work」,FSF、Linus Torvalds(Linux 内核作者)、以及不同法域的法院有不同观点。大多数企业合规团队采取保守立场:只对 LGPL、MPL 这类显式允许链接的库做动态链接,坚决避免专有软件动态链接 GPL 库。

2.5 CMake 中的正确链接示例

下面是一个工程上常见的场景:C++ 程序需要用 OpenSSL(Apache-2.0)和 SQLite(Public Domain),同时动态链接一个 LGPL-2.1 的库(比如 libzip 的某些版本,注意:实际许可请以最新上游为准)。

# CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(myapp CXX)

set(CMAKE_CXX_STANDARD 20)

find_package(OpenSSL REQUIRED)
find_package(SQLite3 REQUIRED)

# 关键:以共享库形式链接 LGPL 库,满足 LGPL-2.1 §6(b)
# 不要改成 libzip::libzip_static
find_package(libzip REQUIRED)

add_executable(myapp
    src/main.cpp
    src/db.cpp
    src/archiver.cpp
)

target_link_libraries(myapp
    PRIVATE
        OpenSSL::SSL
        SQLite::SQLite3
        libzip::zip         # 动态链接,so/dll 可被替换
)

# 在安装包中保留 so 文件为独立实体
install(TARGETS myapp RUNTIME DESTINATION bin)
install(FILES $<TARGET_FILE:libzip::zip> DESTINATION lib)

# 生成 NOTICE 文件,列出 LGPL 库及替换说明
configure_file(
    ${CMAKE_CURRENT_SOURCE_DIR}/NOTICE.in
    ${CMAKE_CURRENT_BINARY_DIR}/NOTICE
    @ONLY
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/NOTICE DESTINATION share/myapp)

对应的 NOTICE.in 模板:

myapp @VERSION@
Copyright (c) 2026 Acme Corp.

This product includes software developed by third parties,
licensed under their respective licenses. In particular:

  libzip (LGPL-2.1-or-later)
    Source: https://libzip.org/
    This product dynamically links libzip. You may replace
    the bundled libzip shared library (lib/libzip.so.5) with
    a modified version of libzip compiled from source.
    See <https://libzip.org/download/> for source code.

  OpenSSL (Apache-2.0)
    Source: https://www.openssl.org/

  SQLite (Public Domain)
    Source: https://sqlite.org/

三、容器镜像:layer 是独立的,镜像作为整体被分发

容器是过去十年最具破坏性的打包方式,也是 Copyleft 讨论里最容易出错的地方。

3.1 FSF 的立场:分发镜像等于传递

GNU 的 GPL FAQ 里明确讨论了「Is distributing a Docker image that includes a GPL-covered program distribution of that program?」。立场是:当然算。镜像只是一种打包格式,把 tar 变成 OCI layer 并不改变「分发」的法律性质。你把 Ubuntu 基础镜像叠上自己的应用 layer,然后 push 到 registry,用户从你的 registry 拉下来,这和给他发一个 tar 包或 USB 盘本质一样。

3.2 基础镜像的许可证:Alpine vs Debian

基础镜像里的每一个包都有自己的许可证。两种常见选择:

Alpine Linux:核心发行版使用 MIT,libc 使用 musl(MIT)。这意味着:基础镜像本身不带 Copyleft 包(除了 BusyBox 的 GPL-2.0,但 BusyBox 是作为独立可执行工具分发的,不作为共享库链接进你的程序)。

Debian / Ubuntu:核心是 Debian Free Software Guidelines 兼容的许可证混合体,glibc 是 LGPL-2.1,大量 GNU 工具是 GPL-2.0 或 GPL-3.0。

你的应用如果只是通过 syscall 调用 glibc(比如 Go 的 CGO、Python 的 C 扩展),业界的主流观点是:调用 LGPL-2.1 的 glibc 不触发整体 Copyleft,因为:

  1. LGPL-2.1 §5 明确把「使用但不包含」的作品排除在 derivative 之外;
  2. 动态链接 glibc 天然满足§6(b) 的「可替换」要求;
  3. 即便是 GPL 应用也明确认可 libc 不是 combined work(GPLv3 §1 的「System Library」例外)。

3.3 GPLv3 的 System Library 例外

GPLv3 §1 定义了「System Libraries」:

The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component…

然后在§6 要求「Corresponding Source」时,明确不需要把 System Library 的源代码包括进去:

The “Corresponding Source” need not include anything that users can regenerate automatically from other parts of the Corresponding Source. … However, it does not include the work’s System Libraries…

这一条从语义上解决了「容器里带 glibc 要不要公开 glibc 源码」的争议:不需要,因为 glibc 是 System Library。但是你自己的应用如果是 GPL,你仍然要提供应用的源码。

3.4 两种 Dockerfile:许可证影响

Alpine 基础镜像

# syntax=docker/dockerfile:1.6
FROM alpine:3.19 AS runtime
# Alpine 核心 MIT,musl libc MIT,不带传染性
# BusyBox GPL-2.0 作为独立工具存在,不与你的应用链接
RUN apk add --no-cache ca-certificates tzdata
WORKDIR /app
COPY --from=build /out/myapp /app/myapp
ENTRYPOINT ["/app/myapp"]
# 合规义务:
# - 如果你修改了 Alpine 的任何 GPL 包,需要提供源码
# - 未修改则遵守 apk 包内的 NOTICE 即可

Debian 基础镜像

# syntax=docker/dockerfile:1.6
FROM debian:12-slim AS runtime
# glibc LGPL-2.1(System Library 例外下通常安全)
# coreutils GPL-3.0(工具级 Copyleft,不与你的应用链接)
RUN apt-get update && apt-get install -y --no-install-recommends \
        ca-certificates tzdata && \
    rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=build /out/myapp /app/myapp

# 合规义务清单(放入镜像层)
COPY NOTICE /usr/share/doc/myapp/NOTICE
COPY WRITTEN-OFFER.txt /usr/share/doc/myapp/WRITTEN-OFFER.txt

ENTRYPOINT ["/app/myapp"]

对应的 WRITTEN-OFFER.txt:

Written Offer for Source Code

This image contains software licensed under the GNU General
Public License (GPL) and related copyleft licenses, including
but not limited to: coreutils, bash, sed, grep.

For a period of three years from the date of distribution of
this image, you may request the complete corresponding source
code of any GPL-licensed component by writing to:

    Acme Corp., Attn: Open Source Compliance
    oss-compliance@acme.example
    123 Example Street, City, Country

We will provide the source code either by mail on a physical
medium (at no charge beyond reasonable shipping cost) or by
pointing you to the exact upstream source used to build this
image, per GPL-3.0 Section 6.

3.5 FROM scratch 与静态二进制:看起来最干净

对 Go/Rust 这类可以产出静态二进制的语言,最干净的做法是:

# syntax=docker/dockerfile:1.6
FROM golang:1.22 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /out/myapp ./cmd/myapp

FROM scratch
COPY --from=build /out/myapp /myapp
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY NOTICE /NOTICE
ENTRYPOINT ["/myapp"]

这个镜像里除了你自己的二进制,什么也没有。只要你的依赖里没有 GPL/LGPL 库(Go 生态主流是 BSD/MIT/Apache-2.0),这是最低合规负担的打包方式。

但有两个坑:

  1. CGO_ENABLED=1 时 Go 会链接 glibc(如果你用了 go-sqlite3 等 CGO 库),从 scratch 切到 Alpine 或 distroless 更稳;
  2. go.sum 中的间接依赖仍然需要 SCA 扫描——静态二进制不会让许可证消失,只是让它们变得不可见。相关实践见 SCA 与 SBOM:如何自动化治理开源合规

3.6 Docker 官方镜像的 GPL 成分

Docker Hub 上的 debianubuntucentos 等官方镜像都附带了完整的包清单与许可证信息。你可以用:

docker run --rm debian:12-slim \
    sh -c "dpkg-query -W -f='\${Package} \${Version} \${Homepage}\n' | head -20"

列出其中所有包。对 Alpine:

docker run --rm alpine:3.19 \
    sh -c "apk info | head -20"

一个合格的 SBOM(Software Bill of Materials)会把这些信息自动化提取。


四、SaaS 部署:AGPL 如何堵住「SaaS 漏洞」

4.1 GPL 的 SaaS 漏洞

GPLv2 是 1991 年写的,那时 Web 服务还不普遍;GPLv3 是 2007 年写的,Web 2.0 已经流行。但 FSF 仍然决定把 GPLv3 的 convey 定义为「副本转移」——显式把「单纯网络交互」排除在外。这就是所谓的 SaaS 漏洞

后果非常戏剧:

这对「贡献回社区」的哲学来说是个漏洞:用户享受到软件带来的服务价值,但永远无法拿到改进后的代码。

4.2 AGPL-3.0 §13:网络交互也触发

AGPL-3.0 在 GPL-3.0 基础上加了一条§13「Remote Network Interaction; Use with the GNU General Public License」:

Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software.

关键条件:

  1. 你修改了程序
  2. 你修改的版本通过网络对远程用户提供交互
  3. 你必须向这些远程用户明显地提供获取修改版源码的机会

通常的做法是在 Web UI 的页脚放一个「Source Code」链接,指向自己的 Git 仓库或 tarball。

4.3 AGPL 的适用场景:数据库、搜索引擎、协作工具

AGPL 的典型使用者是那些「主要以服务形态交付价值」的项目:

AGPL 的选择逻辑:我希望任何人都可以自托管,但云厂商不能简单地把我的代码变成 SaaS 却不回馈改动

4.4 AGPL 的工程触发点:哪怕是一个内部改动

很多团队低估了 AGPL 的穿透性。几个常见陷阱:

  1. 分叉并修 bug:你 fork 了一个 AGPL 项目,修了一个 bug 自己用,还提供给客户通过网络访问——你必须把这个 fork 的源码对客户(远程用户)开放。
  2. 加一个内部管理接口:你在 AGPL 项目里加了一个 /admin 页面,只有公司内部用。这仍然「修改了程序」并「通过网络交互」,严格意义上仍需提供源码,哪怕只对公司内部 IT 员工。
  3. Sidecar 模式:你写了一个 AGPL 程序的 sidecar(比如一个认证代理),sidecar 本身是你的私有代码,但它和 AGPL 主程序是否构成 combined work 取决于交互方式。进程间通过 HTTP/gRPC 交互通常构成;通过共享内存或库链接则可能构成。

4.5 前端也能被 AGPL 传染

一个容易被忽略的场景:前端 JavaScript 是 AGPL 的。因为浏览器从你的服务器下载了 JS bundle,严格说这已经是一次「convey」——用户拿到了副本。在 GPLv3 的语义下,这不算 SaaS,这就是分发

所以:


五、部署场景 × 许可证:触发矩阵

这一节是本文的核心工具。

5.1 五种部署场景

  1. 内部员工使用(不外发):代码/镜像只在公司内部使用,不提供给公司外的任何自然人或法人。
  2. 向客户分发二进制:以任何形式(物理介质、网络下载、容器镜像 pull)把二进制副本提供给客户。
  3. SaaS 服务(用户通过网络访问):你托管服务,用户通过 Web/API 使用,不获得二进制副本。
  4. 嵌入式设备固件:把软件烧进你卖给客户的硬件设备里。
  5. 云服务提供商(将其他人的软件作为服务提供):AWS/阿里云式的托管服务——把上游社区的开源软件作为托管服务卖给客户。

5.2 触发矩阵

部署场景 GPL-2.0 LGPL-2.1 GPL-3.0 AGPL-3.0 SSPL-1.0
内部员工使用(不外发) 不触发 不触发 不触发 不触发 不触发
向客户分发二进制 触发 部分触发 触发 触发 触发
SaaS 服务(用户通过网络访问) 不触发 不触发 不触发 触发 触发整个栈
嵌入式设备固件 触发 部分触发 触发 + 反 Tivoization 触发 触发
云服务提供商托管 不触发 不触发 不触发 触发 触发整个栈

说明:

5.3 嵌入式:TiVo 案例与§6 的「Installation Information」

TiVo 是一家美国数字录像机厂商,2000 年代初大量使用 Linux 内核(GPL-2.0)。他们满足了 GPL-2.0 的义务:提供了内核源码。但设备使用数字签名验证固件,普通用户即使修改了内核源码并编译,也无法把修改后的内核烧进 TiVo 设备——因为签名不匹配。

FSF 认为这违背了 Copyleft 的精神:用户拿到了源码,但无法实际运行修改版。GPL-3.0 §6 因此新增了 Installation Information 条款:

If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term …, the Corresponding Source conveyed under this section must be accompanied by the Installation Information.

这就是为什么 Linux 内核至今停留在 GPL-2.0-only:Linus 和许多内核贡献者不认同 GPLv3 的「anti-Tivoization」,认为设备厂商有权锁定固件。

5.4 云服务商:SSPL 的「触发整个栈」

SSPL-1.0 §13 的原文:

If you make the functionality of the Program or a modified version available to third parties as a service, you must make the Service Source Code available via network download to everyone at no charge, under the terms of this License. Making the functionality of the Program or modified version available to third parties as a service includes, without limitation, enabling third parties to interact with the functionality of the Program or modified version remotely through a computer network… “Service Source Code” means the Corresponding Source for the Program or the modified version, and the Corresponding Source for all programs that you use to make the Program or modified version available as a service, including, without limitation, management software, user interfaces, application program interfaces, automation software, monitoring software, backup software, storage software and hosting software, all such that a user could run an instance of the service using the Service Source Code you make available.

这段「management software, user interfaces, APIs, automation, monitoring, backup, storage, hosting」几乎涵盖了运维整个栈。云厂商如果把 MongoDB(SSPL)作为托管服务卖,严格遵守 SSPL 就意味着要开源自己的整个管控面。这显然是 MongoDB 的设计意图:让大云厂商无法合规地托管 MongoDB

结果:AWS、Azure、Google Cloud 都选择 fork 或换代(AWS DocumentDB 基于早期 MongoDB API;Amazon 的 OpenSearch fork 自 Apache-2.0 时期的 Elasticsearch)。


六、具体工程坑点

下面是六个真实的、容易踩的坑。

6.1 webpack 打包 AGPL 前端库

假设你的前端项目的 package.json 里混进了一个 AGPL 的组件:

{
  "name": "my-frontend",
  "version": "1.0.0",
  "license": "UNLICENSED",
  "dependencies": {
    "react": "^18.2.0",
    "some-agpl-grid": "^5.0.0"
  }
}

webpack build 后产出的 bundle.js 里,AGPL 库的代码被 tree-shaking 后仍然占了相当比例。你部署这个前端到 CDN,任何访问你网站的用户都会收到一份 bundle.js 副本——这是 convey,不是单纯网络交互。

合规动作:

# 1. 用 license-checker 扫描前端依赖
npx license-checker --production --summary

# 2. 设置 CI 检查,禁止 AGPL 进入前端依赖树
npx license-checker --production --failOn "AGPL-3.0;SSPL-1.0"

# 3. 如果必须使用,向供应商购买商业许可

webpack 侧可以配置一个 plugin 自动写 LICENSES 文件:

// webpack.config.js
const LicensePlugin = require('webpack-license-plugin');

module.exports = {
  // ...
  plugins: [
    new LicensePlugin({
      outputFilename: 'oss-licenses.json',
      unacceptableLicenseTest: (licenseId) =>
        ['AGPL-3.0', 'SSPL-1.0', 'GPL-3.0'].includes(licenseId),
    }),
  ],
};

6.2 Docker base image 中的 GPL glibc

前文已经给出了 Alpine 与 Debian 的对比 Dockerfile。补充一点:multi-stage build 的中间镜像也要注意。

错误示例:

# 第一阶段:带 GPL 工具的完整 Debian
FROM debian:12 AS build
RUN apt-get install -y gcc make libreadline-dev
# 你编译了自己的 C++ 程序,并静态链接了 libreadline(GPL-3.0)
RUN gcc -static main.c -o myapp -lreadline

# 第二阶段:看似干净
FROM scratch
COPY --from=build /myapp /myapp

这个看似干净的最终镜像里,myapp 是静态链接的 libreadline——即你的 myapp 整体必须 GPL-3.0。静态链接一个 GPL-3.0 的 C 库是最危险的操作之一。正确做法是换成 BSD 许可的 libedit 或 linenoise。

6.3 Android ROM 中的 GPL 内核

Android 的架构设计有意做了 GPL/非-GPL 隔离:

因此 Android ROM 厂商(小米、OPPO、Samsung、LineageOS)的合规做法是:

  1. 公开修改后的 Linux 内核源码(在 GitHub 上,如 MiCode/Xiaomi_Kernel_OpenSource);
  2. 不公开上层应用框架的专有修改(因为是 Apache-2.0,无此义务);
  3. 公开修改的 GPL 组件(如 BusyBox、mkbootimg 等)。

LineageOS 由于是整个项目开源,合规压力小;商业 ROM 厂商的主要合规点是内核 tag 与厂商分支的及时推送。华为在 OpenHarmony 项目里使用 Linux 内核(LiteOS-A 是自研,HarmonyOS Next 据称完全自研内核以摆脱 GPL 义务,但 OpenHarmony 标准系统仍然使用 Linux 内核)时,也在 gitee.com/openharmony 上维护了对应的 kernel_linux_5.10 仓库以满足 GPL-2.0 义务。

6.4 Java 应用链接 GPL 库

OpenJDK 的 Classpath Exception 让你可以在 GPL JVM 上跑专有 Java 应用。但不是所有 Java 库都带 Classpath Exception

例如:

Maven 侧的检查:

<!-- pom.xml -->
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-enforcer-plugin</artifactId>
  <version>3.4.1</version>
  <executions>
    <execution>
      <id>check-licenses</id>
      <goals><goal>enforce</goal></goals>
      <configuration>
        <rules>
          <bannedDependencies>
            <excludes>
              <!-- 防止引入 GPL 无例外的 Java 库 -->
              <exclude>com.example:pure-gpl-lib</exclude>
            </excludes>
          </bannedDependencies>
        </rules>
      </configuration>
    </execution>
  </executions>
</plugin>

更系统的方案是使用 SCA 工具自动化检查

6.5 动态插件系统:宿主与插件的许可证边界

一个经常被忽略的场景:你的程序有插件机制,用户可以加载第三方 .so/.dll

Linux 内核是这个问题的经典战场:内核是 GPL-2.0,内核模块(.ko)如果和内核紧密耦合就必须 GPL-2.0。但 NVIDIA 闭源驱动多年来使用一个「shim 层」——一个开源的 GPL 小内核模块,通过它与闭源的二进制 blob 通信——来规避这个问题。FSF 明确反对,Linux 社区内部也有持续争议,至今仍是灰色地带。

6.6 SaaS 里的客户端 SDK

如果你做的是 SaaS,但同时给客户发一个 SDK(Python/Java/Go 的 client library),这个 SDK 的许可证就脱离了 AGPL 的网络交互语境——它被 convey 到客户手里,如果 SDK 依赖了 AGPL 库,SDK 就要以 AGPL 形式对外。

实践:SDK 通常采用 Apache-2.0 或 MIT 独立维护,与服务端的 AGPL 代码库严格隔离。


六点五、并排对比:一段真实的开源软件供应链

为了让上面的坑点更具体,下面用一个虚构但写实的产品「Acme Analytics」走一遍典型的开源供应链审计过程。

6.5.1 产品架构

┌─────────────────────────────────────────────────────────┐
│  用户浏览器                                              │
│   └── bundle.js (webpack)                               │
│         ├── React (MIT)                                 │
│         ├── Ant Design (MIT)                            │
│         ├── ECharts (Apache-2.0)                        │
│         └── X-license-unknown-grid (???)  ← 红灯        │
└─────────────────────────────────────────────────────────┘
                         │ HTTPS
┌─────────────────────────────────────────────────────────┐
│  Acme Analytics Server (Go 1.22)                        │
│   ├── gin (MIT)                                         │
│   ├── gorm (MIT)                                        │
│   ├── readline-go (BSD-3-Clause)                        │
│   └── go-ffmpeg-bindings                                │
│         └── CGO → libavcodec (LGPL-2.1 / GPL-2.0)       │
│                        ↑ 动态链接?静态链接?           │
└─────────────────────────────────────────────────────────┘
                         │ TCP
┌─────────────────────────────────────────────────────────┐
│  PostgreSQL 16 (PostgreSQL License, BSD-like)           │
│   └── TimescaleDB extension (Apache-2.0 + TSL)          │
│           TSL: Timescale License (source-available)     │
└─────────────────────────────────────────────────────────┘

6.5.2 审计过程

第一步:前端 SBOM

cd frontend
npx @cyclonedx/cyclonedx-npm --output-format JSON --output-file sbom.json
cat sbom.json | jq '.components[] | {name, version, licenses}' | grep -iE "agpl|gpl|sspl"

输出发现 x-license-unknown-grid 的 license 字段为空——这要么是作者忘了写,要么是故意不写。直接联系作者或从仓库 LICENSE 文件确认;如果是 AGPL,必须下架或替换。

第二步:Go 后端依赖

cd backend
go install github.com/google/go-licenses@latest
go-licenses report ./... > licenses.csv
awk -F',' '$3 ~ /GPL|AGPL/ {print}' licenses.csv

这里的大坑是 CGO 链接的 native 库——go-licenses 只能看到 Go 源码里声明的,看不见 #cgo LDFLAGS: -lavcodec。需要额外手工审计所有 CGO 绑定。

第三步:native 库的许可证核查

# 在构建机器上看实际链接的动态库
ldd ./acme-analytics | grep -iE "avcodec|avformat"

apt-cache show libavcodec59 | grep -E "License|Source"
# 或者在 Debian 上
cat /usr/share/doc/libavcodec59/copyright

FFmpeg 的情况尤其复杂——同一个 FFmpeg 二进制可以编译成 LGPL-2.1 模式或 GPL-2.0 模式,取决于 --enable-gpl 选项。如果用发行版的 libavcodec,通常是 LGPL-2.1(因为发行版要保持商业友好);如果自己从源码编译,要特别注意是否启用了 GPL 模块(如 x264)。

第四步:数据库许可证

PostgreSQL License 是一个 BSD-like 的宽松许可,没有问题。但 TimescaleDB 的结构特殊:

如果你只用 Apache-2.0 部分,无义务;如果用了 TSL 功能,TSL 禁止「提供 TimescaleDB-as-a-Service」——与 SSPL 类似的限制,但用的是不同的机制。

6.5.3 审计结论与行动项

组件 许可证 风险 行动
React, Ant Design, ECharts MIT / Apache-2.0 保留 NOTICE
X-license-unknown-grid 未知 下架或替换
gin, gorm MIT 保留 NOTICE
libavcodec(LGPL 构建) LGPL-2.1 确保动态链接,加替换说明
libavcodec(GPL 构建) GPL-2.0 切换到 LGPL 构建,避免整体 GPL
PostgreSQL PostgreSQL License 保留 NOTICE
TimescaleDB TSL 部分 Timescale License 不提供作为托管服务

6.5.4 产物打包策略

# Multi-stage:build 阶段静态检查许可证,runtime 阶段最小化
FROM golang:1.22 AS build
WORKDIR /src
COPY . .
# 构建前先跑 license check,失败则不出镜像
RUN go install github.com/google/go-licenses@latest && \
    go-licenses check --disallowed_types=forbidden,restricted ./...
RUN CGO_ENABLED=1 go build -o /out/acme ./cmd/acme

FROM debian:12-slim AS runtime
RUN apt-get update && apt-get install -y --no-install-recommends \
        libavcodec59 ca-certificates tzdata && \
    rm -rf /var/lib/apt/lists/*
COPY --from=build /out/acme /usr/local/bin/acme
COPY NOTICE WRITTEN-OFFER.txt /usr/share/doc/acme/
# 关键:不静态链接 libavcodec,让用户可以替换
ENTRYPOINT ["/usr/local/bin/acme"]

七、合规义务清单

不论触发的是哪种 Copyleft,合规核心是三件事:

7.1 保留版权和许可证声明

所有源代码文件的头注释、所有依赖的 LICENSE 文件,都必须原样随产品/镜像分发

project/
├── LICENSE                      # 你自己的许可证
├── NOTICE                       # 你的归属声明
└── third_party/
    ├── libzip/
    │   ├── LICENSE              # LGPL-2.1 原文
    │   └── NOTICE               # libzip 的版权声明
    ├── openssl/
    │   └── LICENSE              # Apache-2.0
    └── ...

7.2 提供源代码(Written Offer)

对 GPL 家族,源代码交付有几种合规形式(见 GPLv3 §6):

书面要约的模板见 3.4 节的 WRITTEN-OFFER.txt。

7.3 NOTICE 文件的写法

一个典型的 NOTICE 文件:

MyApp 2.3.1
Copyright (c) 2024-2026 Acme Corp. All rights reserved.

This product includes the following third-party software:

1. libzip 1.10.1
   Licensed under LGPL-2.1-or-later.
   Source: https://libzip.org/
   Modifications: none
   Replacement: dynamic link (lib/libzip.so.5)

2. OpenSSL 3.2.0
   Licensed under Apache-2.0.
   Source: https://www.openssl.org/
   Modifications: none

3. Linux kernel 6.1.73
   Licensed under GPL-2.0-only.
   Source: git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
   Tag: v6.1.73
   Our patches: https://github.com/acme/linux-acme-6.1/compare/v6.1.73...acme-v6.1.73-rc1

4. BusyBox 1.36.1
   Licensed under GPL-2.0-only.
   Source: https://busybox.net/downloads/busybox-1.36.1.tar.bz2
   Modifications: none

For the complete corresponding source code of any GPL/LGPL
component, please see section 3 of this NOTICE or contact
oss-compliance@acme.example within three years of your receipt
of this product.

7.4 给运维的一张检查表


八、中国案例:OpenHarmony 对 Linux 内核的合规处理

8.1 OpenHarmony 的架构与许可证

OpenHarmony(开放原子开源基金会托管)是华为主导、社区共建的操作系统。它实际包含三种形态:

  1. 轻量系统(LiteOS-M):自研微内核,Apache-2.0;
  2. 小型系统(LiteOS-A):自研,Apache-2.0;
  3. 标准系统:使用 Linux 内核(GPL-2.0-only)+ 自研 HDF 驱动框架(Apache-2.0)+ 自研 ArkUI/ArkTS 框架(Apache-2.0)。

标准系统里内核是 GPL-2.0,这就要求华为及所有分发者必须满足 GPL-2.0 §3 的源码交付义务。

8.2 合规做法

OpenHarmony 在 gitee.com/openharmony 组织下维护了:

对应用层框架(ArkUI、ArkTS 等)则采用 Apache-2.0,避免把上层生态传染进 GPL。这是一种典型的「底层 GPL,上层 Apache-2.0」的隔离架构,与 Android 的思路一致。

8.3 HarmonyOS Next 的内核重写

根据华为 2024 年的公开说明,HarmonyOS Next(面向终端设备的商业发行版)据称完全采用自研 HongMeng 内核,不再使用 Linux。如果属实,这会让 HarmonyOS Next 完全脱离 GPL-2.0 的义务链条——这不是许可证层面的违规,而是通过工程替换合法地绕开 Copyleft

与此类似,许多厂商在战略层面选择:

这种选择的动机是减少对 GPL 生态的长期依赖——而不是反对开源。

8.4 反面案例:红芯浏览器事件

2018 年,北京红芯时代科技宣称发布「自主可控」的红芯浏览器,后被发现实际是 Chromium 的壳。这是一个许可证层面完全合规但技术叙事严重误导的典型案例:

详细分析见 红芯 / 深度等国产软件的合规争议

这个案例的开源工程学启示:宽松许可证给你提供了最大的商业自由,但不能替代对用户的诚信


八点五、延伸:Copyleft 与现代开发模式的张力

8.5.1 Monorepo 与 Copyleft 边界

现代开发普遍使用 monorepo:一个巨大的 Git 仓库里放着几十个独立服务。Copyleft 边界在 monorepo 里变得微妙:

Google 的 monorepo 策略:内部工具库永远使用 Apache-2.0(或 BSD-3-Clause),不允许把 GPL/AGPL 代码放进 //third_party。这不是法律要求,而是工程上为了避免跨项目污染。

8.5.2 微服务、gRPC 与 Copyleft

微服务架构下,服务之间通过 HTTP/gRPC 通信,每个服务是独立的进程、独立的二进制。FSF 明确表态:通过进程间通信(pipe、socket、RPC、REST)交互的程序不构成 combined work(GPL FAQ: “CommunicatingWithNonfreeProgram”)。

这意味着:

注意远程用户的定义:AGPL-3.0 §13 说的是「users interacting with it remotely through a computer network」。如果是你公司内部的网关代为转发用户请求,那么 AGPL 义务仍然指向最终用户——他们是真正的 remote user。但这里的因果链有点绕,法律上尚未有清晰判例。

8.5.3 Kubernetes Operator 与 CRD 的许可证传染

许多开源项目以 Kubernetes Operator 形式发布(Prometheus Operator、Cert-Manager、ArgoCD 等)。Operator 本身是一个进程,通过 Kubernetes API 读写 CRD(Custom Resource Definition)。

8.5.4 AI / LLM 时代的新问题

2023 年以来一个新兴的问题:大语言模型(LLM)训练数据里包含大量 GPL 代码,模型输出的代码片段是否继承 GPL?

这是一个活跃争议:

工程上的保守做法:


九、选型建议

基于上面的分析,给出一份面向不同场景的许可证选型建议。

9.1 我要发布一个库,希望被广泛使用

9.2 我要发布一个应用,希望强制下游回馈

9.3 我要链接一个我不想被传染的库

9.4 我要打包 Docker 镜像

9.5 我是云服务商

9.6 我做嵌入式


十、参考资料

10.1 许可证原文

  1. GNU General Public License, version 2. https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  2. GNU General Public License, version 3. https://www.gnu.org/licenses/gpl-3.0.html
  3. GNU Lesser General Public License, version 2.1. https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
  4. GNU Lesser General Public License, version 3. https://www.gnu.org/licenses/lgpl-3.0.html
  5. GNU Affero General Public License, version 3. https://www.gnu.org/licenses/agpl-3.0.html
  6. Server Side Public License, version 1. https://www.mongodb.com/licensing/server-side-public-license

10.2 官方解释与 FAQ

  1. Frequently Asked Questions about the GNU Licenses. https://www.gnu.org/licenses/gpl-faq.html
  2. SPDX License List. https://spdx.org/licenses/
  3. OpenJDK GPL v2 + Classpath Exception. https://openjdk.org/legal/gplv2+ce.html
  4. Linux Kernel Licensing Rules. https://www.kernel.org/doc/html/latest/process/license-rules.html
  5. Open Source Initiative, “The Open Source Definition”. https://opensource.org/osd

10.3 工程实践与案例

  1. OpenHarmony Kernel Linux. https://gitee.com/openharmony/kernel_linux_5.10
  2. Xiaomi Kernel OpenSource. https://github.com/MiCode/Xiaomi_Kernel_OpenSource
  3. LineageOS Source. https://github.com/LineageOS
  4. FFmpeg Legal. https://www.ffmpeg.org/legal.html
  5. Docker Official Images Licensing. https://github.com/docker-library/official-images

10.4 学术与书籍

  1. Eben Moglen, “Free Software and the Death of Copyright,” 2001.
  2. Heather Meeker, “Open (Source) for Business: A Practical Guide to Open Source Software Licensing,” 2020.
  3. Lawrence Rosen, “Open Source Licensing: Software Freedom and Intellectual Property Law,” 2004.
  4. Andrew M. St. Laurent, “Understanding Open Source and Free Software Licensing,” O’Reilly, 2004.

10.5 工具

  1. go-licenseshttps://github.com/google/go-licenses
  2. license-checker (npm) — https://github.com/davglass/license-checker
  3. CycloneDX — https://cyclonedx.org/
  4. SPDX Tools — https://github.com/spdx/tools
  5. FOSSology — https://www.fossology.org/

十一、本系列其他文章


免责声明:本文为工程技术文章,提供的是面向工程师的 Copyleft 判定思路与工程实践参考,不构成任何形式的法律意见。具体的合规事宜请咨询专业的知识产权律师或法务顾问。不同法域(美国、欧盟、中国大陆)对「衍生作品」「分发」「网络交互」的法律解释可能不一致,商业决策时请以当地法律与专业法律建议为准。文中提及的具体项目、公司与许可证信息基于写作时的公开资料,可能随时间变化,请以上游与官方最新公告为准。


上一篇开源许可证全景:宽松、弱 Copyleft、强 Copyleft、网络 Copyleft

下一篇专利授权与商标:Apache 2.0、GPLv3 与「兼容性」陷阱

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。


By .