# 与 Docker 服务容器通信

了解如何使用 Docker 服务容器将数据库、Web 服务、内存缓存和其他工具连接到你的工作流。

## 与 Docker 服务容器通信

服务容器是 Docker 容器，以简便、可携带的方式托管您可能需要在工作流程中测试或操作应用程序的服务。 例如，您的工作流程可能必须运行需要访问数据库和内存缓存的集成测试。

您可以为工作流程中的每个作业配置服务容器。
GitHub 为工作流中配置的每个服务创建一个新的 Docker 容器，并在作业完成后销毁服务容器。 作业中的步骤可与属于同一作业的所有服务容器通信。 但是，你不能在组合操作中创建和使用服务容器。

> \[!NOTE]
> 如果工作流使用 Docker 容器操作、作业容器或服务容器，则必须使用 Linux 运行器：
>
> * 如果您要使用 GitHub 托管的运行器，则必须使用 Ubuntu 运行器。
> * 如果您要使用自托管运行器，则必须使用 Linux 机器作为运行器，并且必须安装 Docker。

您可以在工作流程中配置作业直接在运行器机器或 Docker 容器上运行。 作业与其服务容器之间的通信根据作业是直接在运行器上运行还是在容器中运行而有所不同。

### 在容器中运行作业

在容器中运行作业时， GitHub 使用 Docker 的用户定义网桥网络将服务容器连接到作业。 有关详细信息，请参阅 Docker 文档中的“[桥网络驱动程序](https://docs.docker.com/engine/network/drivers/bridge/)”。

在容器中运行作业和服务可简化网络访问。 您可以使用工作流程中配置的标签访问服务容器。 服务容器的主机名自动映射到标签名称。 例如，如果创建带有标签 `redis` 的服务容器，则该服务容器的主机名为 `redis`。

您无需为服务容器配置任何端口。 默认情况下，属于同一 Docker 网络的所有容器会相互显示所有端口，但在 Docker 网络外部不会显示任何端口。

### 在运行器机器上运行作业

直接在运行程序计算机上运行作业时，你可以使用 `localhost:<port>` 或 `127.0.0.1:<port>` 访问服务容器。
GitHub 配置容器网络以启用从服务容器到 Docker 主机的通信。

当作业直接在运行器机器上运行时， Docker 容器中运行的服务默认情况下不会向运行器上的作业显示其端口。 您需要将服务容器上的端口映射到 Docker 主机。 有关详细信息，请参阅“[与 Docker 服务容器通信](/zh/actions/using-containerized-services/about-service-containers#mapping-docker-host-and-service-container-ports)”。

## 创建服务容器

可以使用 `services` 关键字创建作为工作流作业的一部分的服务容器。 有关详细信息，请参阅 [`jobs.<job_id>.services`](/zh/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idservices)。

此示例在名为 `redis` 的作业中创建名为 `container-job` 的服务。 此示例中的 Docker 主机是 `node:16-bullseye` 容器。

```yaml copy
name: Redis container example
on: push

jobs:
  # Label of the container job
  container-job:
    # Containers must run in Linux based operating systems
    runs-on: ubuntu-latest
    # Docker Hub image that `container-job` executes in
    container: node:16-bullseye

    # Service containers to run with `container-job`
    services:
      # Label used to access the service container
      redis:
        # Docker Hub image
        image: redis
```

## 映射 Docker 主机和服务容器端口

如果作业在 Docker 容器中运行，则不需要映射主机或服务容器上的端口。 如果作业直接在运行器机器上运行，则需要将任何必需的服务容器端口映射到主机运行器机器上的端口。

可以使用 `ports` 关键字将服务容器端口映射到 Docker 主机。 有关详细信息，请参阅 [`jobs.<job_id>.services`](/zh/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idservices)。

| `ports` 的值    | 说明                                      |
| ------------- | --------------------------------------- |
| `8080:80`     | 将容器中的 TCP 端口 80 映射到 Docker 主机上的端口 8080。 |
| `8080:80/udp` | 将容器中的 UDP 端口 80 映射到 Docker 主机上的端口 8080。 |
| `8080/udp`    | 将 Docker 主机中随机选择的端口映射到容器中的 UDP 端口 8080。 |

使用 `ports` 关键字映射端口时， GitHub 使用 `--publish` 命令将容器的端口发布到 Docker 主机。 有关详细信息，请参阅 Docker 文档中的“[Docker 容器网络](https://docs.docker.com/config/containers/container-networking/)”。

指定容器端口但不指定 Docker 主机端口时，容器端口将随机分配给空闲端口。
GitHub 在服务容器上下文中设置分配的容器端口。 例如，对于 `redis` 服务容器，如果配置了 Docker 主机端口 5432，则可以使用 `job.services.redis.ports[5432]` 上下文访问对应的容器端口。 有关详细信息，请参阅“[上下文参考](/zh/actions/learn-github-actions/contexts#job-context)”。

### 映射 Redis 端口的示例

此示例将服务容器 `redis` 端口 6379 映射到 Docker 主机端口 6379。

```yaml copy
name: Redis Service Example
on: push

jobs:
  # Label of the container job
  runner-job:
    # You must use a Linux environment when using service containers or container jobs
    runs-on: ubuntu-latest

    # Service containers to run with `runner-job`
    services:
      # Label used to access the service container
      redis:
        # Docker Hub image
        image: redis
        #
        ports:
          # Opens tcp port 6379 on the host and service container
          - 6379:6379
```

## 使用映像注册表进行身份验证

如果需要使用映像注册表进行身份验证，则可以指定服务容器的凭据。 这允许您使用来自私有注册表的映像或[增加您的 DockerHub 配额限制](https://www.docker.com/increase-rate-limits/)。

下面是使用 Docker 中心和 GitHubContainer registry以下项进行身份验证的示例：

```yaml copy
jobs:
  build:
    services:
      redis:
        # Docker Hub image
        image: redis
        ports:
          - 6379:6379
        credentials:
          username: ${{ secrets.dockerhub_username }}
          password: ${{ secrets.dockerhub_password }}
      db:
        # Private registry image
        image: ghcr.io/octocat/testdb:latest
        credentials:
          username: ${{ github.repository_owner }}
          password: ${{ secrets.ghcr_password }}
```

## 自定义服务容器入口点和命令

默认情况下，服务容器使用 Docker 映像中定义的入口点和命令运行。 可以使用`entrypoint` 键和`command` 键来覆盖这些设置。 当需要将标志传递到服务（如数据库）或完全交换映像入口点而不生成自定义包装器映像时，这非常有用。

该 `command` 键将替代映像的默认命令（`CMD`）。 大多数场景仅需 `command` — 镜像已包含正确入口点，只需传递标志：

```yaml copy
services:
  mysql:
    image: mysql:8
    command: --sql_mode=STRICT_TRANS_TABLES --max_allowed_packet=512M
    env:
      MYSQL_ROOT_PASSWORD: test
    ports:
      - 3306:3306
```

该 `entrypoint` 键替代图像的 `ENTRYPOINT`。 可以将 `command` 与参数结合，以传递给自定义入口点：

```yaml copy
services:
  etcd:
    image: quay.io/coreos/etcd:v3.5.17
    entrypoint: etcd
    command: >-
      --listen-client-urls http://0.0.0.0:2379
      --advertise-client-urls http://0.0.0.0:2379
    ports:
      - 2379:2379
```

命名和行为与 Docker Compose 匹配。 有关详细信息，请参阅 [`jobs.<job_id>.services.<service_id>.command`](/zh/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idservicesservice_idcommand) 和 [`jobs.<job_id>.services.<service_id>.entrypoint`](/zh/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idservicesservice_identrypoint)。

## 其他阅读材料

* [创建 Redis 服务容器](/zh/actions/using-containerized-services/creating-redis-service-containers)
* [创建 PostgreSQL 服务容器](/zh/actions/using-containerized-services/creating-postgresql-service-containers)