---
url: /zh/build/script-vs-plugin.md
---
## 概述

在 CNB 流水线中，`Job`（任务）是最小的执行单元，主要分为**脚本任务**、**插件任务**和**内置任务**三种类型。前两者都支持指定 `image` 参数定义运行环境，这容易让新人混淆。本文档将详细对比这两种任务类型的特点和使用场景，帮助你做出合适的选择。

## 相同点

### 执行环境

* **两者都可以指定 `image` 参数**来定义运行环境
* **都在独立的容器环境中执行**，避免污染宿主或其他任务环境

### 环境变量

* **都可以使用 CNB 内置的系统环境变量**，如 `CNB_REPO_SLUG`、`CNB_COMMIT` 等

## 区别对比

### 核心差异

| 特性 | 脚本任务 | 插件任务 |
|------|----------|----------|
| **执行方式** | 以 `image` 作为执行环境，通过 `script` 或 `commands` 参数指定要执行的 Shell 命令 | 运行 `image` 中内置的 `ENTRYPOINT` 命令，可视为一个功能模块 |
| **默认环境** | 若不指定 `image`，则在流水线容器环境中执行 | 必须指定 `image`，运行插件镜像内置的入口命令 |
| **参数传递** | 通过 `env` 或 `imports` 声明或引入环境变量 | 通过 `settings` 指定参数，参数会以 `PLUGIN_` 接参数名大写形式注入为环境变量 |
| **环境变量限制** | 无限制，可自由使用自定义环境变量 | 可使用 CNB 内置的系统环境变量，但通过 `env` 或 `imports` 声明或引入的自定义环境变量不会传递给插件 |
| **灵活性** | 灵活性高，用户可指定任意 Shell 命令 | 功能由插件开发者预设，通过 `settings` 配置 |
| **复用性** | 往往和业务强相关，使用限制较多 | 功能内聚易复用，可分享到插件市场 |
| **版本控制** | 脚本逻辑随代码仓库版本控制 | 若未指定 `image` 版本，默认使用 `latest`，插件更新可能导致已有流水线失败 |

### 详细说明

#### 执行方式

**脚本任务**：以镜像作为执行环境，你拥有完全的控制权，可以执行任意 Shell 命令。

```yaml
- name: install dependencies
  image: node:20 # 指定执行环境，若不指定则在流水线容器环境中执行
  script: npm install # 执行 Shell 命令
  env:
    NODE_ENV: production # 通过 env 声明环境变量
```

**插件任务**：运行插件镜像中预设的 `ENTRYPOINT` 命令，通过 `settings` 参数配置插件行为。

```yaml
- name: npm publish
  image: plugins/npm # 运行插件镜像，执行内置的 ENTRYPOINT
  settings:
    username: $NPM_USER # 通过 settings 指定参数
    password: $NPM_PASS
    registry: https://registry.npmjs.org/
```

上述插件任务会在环境中注入：

* `PLUGIN_USERNAME`
* `PLUGIN_PASSWORD`
* `PLUGIN_REGISTRY`

#### 参数传递

**脚本任务**的环境变量传递：

* 通过 `env` 声明环境变量
* 通过 `imports` 从文件导入环境变量
* 优先级：`Job env` > `Stage env` > `Pipeline env`

**插件任务**的参数传递：

* 通过 `settings` 指定参数（参数会以 `PLUGIN_` 前缀注入环境变量）
* 通过 `settingsFrom` 从文件加载参数
* 通过 `args` 传递命令行参数
* **注意**：`env` 和 `imports` 声明的自定义环境变量不会传递给插件

#### 环境变量限制

**脚本任务**：

* ✅ 可以使用 `env` 声明的自定义环境变量
* ✅ 可以使用 `imports` 导入的环境变量
* ✅ 可以使用 CNB 内置的系统环境变量

**插件任务**：

* ❌ 不能使用 `env` 声明的自定义环境变量（不会传递）
* ❌ 不能使用 `imports` 导入的环境变量（不会传递）
* ✅ 可以使用 CNB 内置的系统环境变量
* ✅ 可以通过变量替换在 `settings` 和 `args` 中引用环境变量

```yaml
# 正确：在 settings 中引用环境变量
- name: correct plugin usage
  image: plugins/npm
  settings:
    username: $NPM_USER # ✅ 正确：引用环境变量进行变量替换
    password: $NPM_PASS

# 错误：试图通过 env 传递参数给插件
- name: wrong plugin usage
  image: plugins/npm
  env:
    PLUGIN_USERNAME: $NPM_USER # ❌ 错误：这不会传递给插件
    PLUGIN_PASSWORD: $NPM_PASS
```

## 最佳实践

### 1. 版本控制

**插件任务**：始终指定镜像版本，避免使用 `latest` 标签，防止插件更新导致流水线失败。

```yaml
# 推荐：指定版本
- name: npm publish
  image: plugins/npm:1.2.3
  settings:
    username: $NPM_USER

# 不推荐：使用 latest（插件更新可能导致配置失败）
- name: npm publish
  image: plugins/npm # 默认使用 latest
  settings:
    username: $NPM_USER
```

**脚本任务**：脚本逻辑随代码仓库版本控制，无需额外关注。

### 2. 安全性考虑

**插件任务**：

* 使用官方或可信的插件镜像
* 定期检查插件镜像的安全更新
* 在密钥仓库中存储敏感信息

**脚本任务**：

* 避免在脚本中硬编码敏感信息
* 使用环境变量传递密钥
* 使用密钥仓库管理配置

### 3. 复用性考虑

**插件任务**适合封装可复用的功能：

* 消息通知（企业微信、飞书、钉钉等）
* 通用 CI/CD 操作（发布、部署等）
* 代码质量检查（lint、扫描等）

这些功能可以发布到 CNB 插件市场，供其他开发者选用。

**脚本任务**适合业务相关的逻辑：

* 代码构建和测试
* 自定义数据处理
* 特定业务流程

这些逻辑往往与项目强相关，复用性较低。

### 4. 选择建议

| 场景 | 推荐类型 | 原因 |
|------|----------|------|
| 通用 CI/CD 操作（发布、部署） | 插件任务 | 插件市场有成熟方案，易于复用 |
| 消息通知（企微、飞书、钉钉） | 插件任务 | 功能内聚，配置简单 |
| 代码构建和测试 | 脚本任务 | 灵活控制，适应各种构建工具 |
| 自定义业务逻辑 | 脚本任务 | 需要完全控制执行流程 |
| 调用外部 API | 脚本任务 | 灵活处理各种 API 格式 |
| 需要复用的通用功能 | 插件任务 | 可分享到插件市场 |

## 常见问题

### Q1: 为什么插件任务不能通过 env 传递环境变量？

**A**: 插件任务的设计理念是功能模块化，通过 `settings` 明确定义插件接受的参数，而不是传递所有环境变量。这样做的好处是：

* 插件接口清晰，避免参数混乱
* 插件可以明确声明需要哪些参数
* 提高安全性和可维护性

### Q2: 脚本任务不指定 image 会在哪里执行？

**A**: 脚本任务不指定 `image` 时，会在流水线容器环境中执行。流水线容器环境由 `Pipeline.docker.image` 或 `Stage.image` 指定。

```yaml
main:
  push:
    - docker:
        image: node:20 # 流水线容器环境
      stages:
        - name: without image
          # 不指定 image，在 node:20 环境中执行
          script: node -v

        - name: with image
          # 指定 image，在 python:3.9 环境中执行
          image: python:3.9
          script: python --version
```

### Q3: 如何在插件任务中使用环境变量？

**A**: 在 `settings` 或 `args` 中使用变量替换。

```yaml
- name: plugin with env
  image: plugins/npm
  settings:
    username: $NPM_USER # 使用变量替换
    password: $NPM_PASS
```

### Q4: 插件任务可以执行自定义脚本吗？

**A**: 插件任务的执行逻辑由插件镜像预设，不支持执行自定义脚本。如需执行自定义脚本，应使用脚本任务。

### Q5: 如何判断应该使用脚本任务还是插件任务？

**A**: 问自己以下问题：

1. 是否有现成的插件满足需求？ → 使用插件任务
2. 是否需要灵活执行自定义命令？ → 使用脚本任务
3. 功能是否可以复用和分享？ → 考虑使用插件任务
4. 功能是否与业务强相关？ → 使用脚本任务

## 总结

脚本任务和插件任务各有优势：

* **脚本任务**：灵活性高，适合自定义业务逻辑
* **插件任务**：功能内聚，易于复用和分享

选择合适的任务类型可以简化配置、提高复用性，同时保持必要的灵活性。
