> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreweave.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 샌드박스에서 tailnet 서비스에 연결하기

> Tailscale 프록시를 통해 샌드박스 이그레스를 라우팅하여 tailnet에만 있는 서비스에 연결합니다.

Tailscale 클러스터 이그레스를 사용하면 자체 CoreWeave Kubernetes Service(CKS) 클러스터의 샌드박스에서 tailnet의 디바이스에 연결할 수 있습니다. 예를 들어 내부 API, 사내 네트워크의 데이터베이스, 워크스테이션의 모델 서버 등에 연결할 수 있습니다. Tailscale Kubernetes Operator는 tailnet 디바이스를 Kubernetes Service로 게시하고, 샌드박스 이그레스 규칙은 샌드박스가 해당 Service 뒤의 프록시에 액세스할 수 있도록 권한을 부여합니다.

이 페이지는 CKS 클러스터에서 관리형 샌드박스 러너를 운영하는 관리자를 대상으로 합니다. 이 예시에서는 Operator가 `tailscale` namespace에 설치된 독립형 이그레스 구성을 사용합니다. 설정 방식은 다음 두 가지 플랫폼 동작에 따라 달라집니다.

* Tailscale Operator를 통해 클러스터 내부 Service를 노출하면 tailnet 디바이스가 해당 Service에 연결할 수 있습니다. 반대로 파드에서 tailnet 디바이스로 아웃바운드 연결을 하려면 [Tailscale의 클러스터 이그레스 기능](https://tailscale.com/kb/1438/kubernetes-operator-cluster-egress)이 필요합니다.
* 샌드박스 이그레스 규칙은 기본적으로 모든 트래픽을 거부하는 베이스라인 위에 허용 규칙만 추가하는 방식이며, `EgressRule(any=True)`는 클러스터 내부 주소를 포함하지 않습니다. 따라서 Tailscale 프록시와 같은 클러스터 내부 대상에는 `selector` 또는 `tenant` 규칙을 통해서만 연결할 수 있습니다.

## 사전 요구 사항

* `installStatus`가 `READY`인 관리형 샌드박스 러너가 있는 CKS 클러스터. [자체 컴퓨팅 사용하기](https://docs.coreweave.com/ko/products/sandboxes/get-started-cks.md)를 참조하세요.
* 해당 클러스터에 설치된 Tailscale Kubernetes 오퍼레이터와 CKS용으로 설정한 tailnet 정책 파일. [Tailscale Kubernetes 오퍼레이터 배포하기](https://docs.coreweave.com/products/cks/auth-access/tailscale-operator.md)를 참조하세요.
* 연결하려는 tailnet 디바이스의 MagicDNS 이름(예: `db-host.[TAILNET-NAME].ts.net`)과 서비스가 수신 대기하는 포트.
* 이 셀렉터 이그레스 규칙을 허용하는 러너 정책. `any` 권한은 셀렉터에 적용되지 않으므로, `allowed_egress`가 비어 있지 않은 정책에는 형식이 일치하는 `selector` 권한이 반드시 있어야 합니다. 즉, 이 권한에는 `namespace_labels`도 설정되어 있어야 하고, 해당 레이블은 샌드박스가 선언한 레이블의 부분집합이어야 하며, 포트 필터는 TCP 포트 8080을 포함해야 합니다. [`SelectorBlock`](https://docs.coreweave.com/ko/products/sandboxes/reference/profile#selectorblock)을 참조하세요.
* [CKS 배치용 사용자 인증 정보](https://docs.coreweave.com/ko/products/sandboxes/placement.md)로 설정한 `cwsandbox` Python SDK, 그리고 `curl`과 `getent`가 설치된 샌드박스 이미지.
* `default` namespace에서 Service를 생성하거나 업데이트하고 Operator의 namespace에서 파드 목록을 조회할 수 있는 `kubectl` 액세스 권한.
* 이그레스 프록시가 대상 디바이스와 애플리케이션 포트에 연결하도록 허용하는 tailnet 정책.

## Tailscale 이그레스 Service 만들기

tailnet 디바이스의 MagicDNS 이름을 주석으로 단 `ExternalName` Service를 만드세요. Operator는 자체 namespace에 프록시 파드와 헤드리스 Service를 만든 다음, `ExternalName` Service가 해당 헤드리스 Service를 가리키도록 설정합니다. 예시에서는 링크된 설치 가이드와 마찬가지로 `tailscale` namespace를 사용합니다. Operator를 다른 namespace에 설치했다면 아래의 명령어와 `namespace_labels`를 수정하세요.

`[TAILNET-FQDN]`을 디바이스의 MagicDNS 이름으로 바꾸세요. 이 예제는 `default`에 Service를 만들고, 샌드박스가 다른 namespace에서도 연결할 수 있도록 Service의 완전 수식된 DNS 이름을 사용합니다. 또한 포트 8080에서 `/health` 엔드포인트를 제공하는 HTTP 서비스를 전제로 합니다. 사용 중인 서비스에 맞게 포트와 경로를 조정하세요.

```yaml title="tailnet-egress.yaml" theme={"system"}
apiVersion: v1
kind: Service
metadata:
  name: internal-api
  namespace: default
  annotations:
    tailscale.com/tailnet-fqdn: "[TAILNET-FQDN]"
spec:
  type: ExternalName
  externalName: placeholder.tailscale.internal # Kubernetes 필수 항목이며, Operator가 덮어씁니다
```

```bash theme={"system"}
kubectl apply -f tailnet-egress.yaml
```

샌드박스는 `internal-api.default.svc.cluster.local:8080`에 연결합니다. 짧은 이름인 `internal-api:8080`은 해당 Service와 같은 namespace 안에서만 사용할 수 있습니다. 클러스터에서 `cluster.local`이 아닌 다른 DNS 접미사를 사용하는 경우 예시의 해당 접미사를 바꿔서 사용하세요.

Operator가 프록시 파드를 생성했는지 확인하세요.

```bash theme={"system"}
kubectl get pods -n tailscale \
  -l 'tailscale.com/parent-resource=internal-api,tailscale.com/parent-resource-ns=default'
```

프록시가 준비 완료 상태가 될 때까지 기다리세요. 출력 예시(생성되는 파드 이름은 다를 수 있음):

```text theme={"system"}
NAME                     READY   STATUS    RESTARTS   AGE
ts-internal-api-k4x7p-0  1/1     Running   0          32s
```

## 샌드박스에 프록시로의 이그레스 권한 부여

`EgressRule`에서 레이블로 프록시 파드를 선택하세요. 이 규칙에는 두 레이블 세트가 모두 필요합니다.

* `pod_labels`는 Operator의 `tailscale.com/parent-resource` 및 `tailscale.com/parent-resource-ns` 레이블을 사용해, 프록시가 앞단에서 받아 주는 Service를 기준으로 프록시를 식별합니다. 다른 namespace에 있는 같은 이름의 Service용 프록시까지 규칙에 일치하지 않도록 namespace 레이블을 포함하세요.
* `namespace_labels`는 `tailscale` namespace를 선택합니다. 이 레이블이 없으면 규칙이 샌드박스 자체의 namespace에만 적용되므로 다른 namespace에 있는 프록시에 도달할 수 없습니다.

`ports`는 서비스가 수신 대기하는 포트와 동일하게 설정하세요. `[RUNNER-ID]`는 Service를 생성한 클러스터에 있는 러너의 ID로 바꾸세요. `runner_ids`를 지정하지 않으면 CKS 배치 과정에서 조직 내 조건에 맞는 러너 중 어느 것이든 사용될 수 있으며, 프록시가 없는 클러스터의 러너가 선택될 수도 있습니다.

```python theme={"system"}
from cwsandbox import EgressRule, NetworkOptions, Sandbox, SelectorBlock

tailscale_egress = EgressRule(
    selector=SelectorBlock(
        pod_labels={
            "tailscale.com/parent-resource": "internal-api",
            "tailscale.com/parent-resource-ns": "default",
        },
        namespace_labels={"kubernetes.io/metadata.name": "tailscale"},
    ),
    ports=[8080],
)

with Sandbox.run(
    placement_mode="cks",
    runner_ids=["[RUNNER-ID]"],
    network=NetworkOptions(egress=[tailscale_egress]),
) as sandbox:
    result = sandbox.exec([
        "curl", "-sS", "--include", "--max-time", "15",
        "http://internal-api.default.svc.cluster.local:8080/health",
    ]).result()
    print(result.stdout)
    print(result.stderr)
```

이 셀렉터와 포트는 독립형 이그레스용입니다. 이그레스 `ProxyGroup`은 공유 프록시 파드를 사용하고 포트를 다시 매핑하므로, 이 규칙만으로는 해당 모드를 완전히 설정할 수 없습니다. [Tailscale의 고가용성 이그레스 가이드](https://tailscale.com/docs/kubernetes-operator/egress#high-availability-ha-egress)를 참조하세요.

러너 정책이 `dns_egress` 제약 조건에서 DNS 이그레스를 거부하지 않는 한 클러스터 DNS에는 계속 접근할 수 있습니다. 따라서 샌드박스는 별도의 규칙 없이도 Service 이름을 확인할 수 있습니다.

## 연결 확인

이 예시는 HTTP 응답 헤더와 본문을 출력하며, curl 오류가 발생하면 해당 오류도 함께 출력합니다. 상태 200을 반환하는 health 엔드포인트라면 `HTTP/1.1 200 OK`와 같은 상태 줄에 이어 서비스의 health 응답이 표시됩니다. 401이나 404 같은 HTTP 오류가 표시되면 HTTP 서버가 응답했다는 뜻이므로, 애플리케이션 인증이나 요청한 경로를 확인하세요.

timeout 또는 연결 오류는 샌드박스와 프록시 사이, 프록시와 tailnet 디바이스 사이, 또는 애플리케이션에서 발생할 수 있습니다. DNS와 HTTP 교환을 따로 확인하려면 러너 ID를 지정하여 다음 진단 예시를 실행하세요.

```python theme={"system"}
from cwsandbox import EgressRule, NetworkOptions, Sandbox, SelectorBlock

tailscale_egress = EgressRule(
    selector=SelectorBlock(
        pod_labels={
            "tailscale.com/parent-resource": "internal-api",
            "tailscale.com/parent-resource-ns": "default",
        },
        namespace_labels={"kubernetes.io/metadata.name": "tailscale"},
    ),
    ports=[8080],
)

with Sandbox.run(
    placement_mode="cks",
    runner_ids=["[RUNNER-ID]"],
    network=NetworkOptions(egress=[tailscale_egress]),
) as sandbox:
    dns_result = sandbox.exec([
        "getent", "hosts", "internal-api.default.svc.cluster.local",
    ]).result()
    print(dns_result.stdout)
    print(dns_result.stderr)
    http_result = sandbox.exec([
        "curl", "-sv", "--max-time", "15", "-o", "/dev/null",
        "http://internal-api.default.svc.cluster.local:8080/health",
    ]).result()
    print(http_result.stderr)
```

DNS 조회에 성공하면 주소가 하나 이상 출력됩니다. curl 출력에서 프록시 주소로 연결되었는지, HTTP 응답 상태가 표시되는지 확인하세요. DNS 조회에 성공했다고 해서 프록시가 대상에 연결할 수 있는 것은 아닙니다.

## 문제 해결

| 증상 | 가능한 원인 | 해결 방법 |
| - | - | - |
| 이그레스 규칙 때문에 생성이 실패함 | `allowed_egress`가 비어 있지 않은데, 셀렉터의 레이블, namespace 레이블 또는 요청한 포트가 여기에 포함되지 않았습니다. | 정책 Admin에게 셀렉터를 허용해 달라고 요청하세요. 이때 namespace 형식이 일치해야 하고, 레이블은 선언된 레이블의 하위 집합이어야 하며, 포트 필터에 TCP 8080이 포함되어야 합니다. |
| 이름이 확인되지 않음 | DNS 이름의 namespace 또는 클러스터 접미사가 잘못되었거나, Operator가 아직 Service를 설정하지 않았거나, DNS 이그레스가 거부되었습니다. | Service의 `externalName`을 확인하고, 전체 Service 이름을 사용하고, 러너의 DNS 이그레스 정책을 확인하세요. |
| 이름은 확인되지만 연결 시간이 초과됨 | 규칙이 잘못된 파드나 namespace를 선택했거나, tailnet 정책이 대상을 차단하고 있거나, 대상이 수신 대기 중이 아닙니다. | 프록시의 레이블과 namespace, tailnet 액세스 규칙, 대상 애플리케이션의 주소와 포트를 확인하세요. |
| 한 포트에서만 연결됨 | 이그레스 규칙의 `ports` 목록에 나머지 포트가 빠져 있습니다. | 누락된 포트를 이그레스 규칙에 추가하세요. |
| `tailscale` namespace에 프록시 파드가 나타나지 않음 | Operator가 다른 namespace에 설치되었거나, 조정에 실패했거나, Operator의 사용자 인증 정보 또는 태그가 승인되지 않았습니다. | Operator namespace와 로그를 확인한 다음 [Operator 태그 설정](https://docs.coreweave.com/products/cks/auth-access/tailscale-operator.md#configure-operator-tags)을 참조하세요. |

## 관련 페이지

* [Tailscale Kubernetes Operator 배포](https://docs.coreweave.com/products/cks/auth-access/tailscale-operator.md): CKS에 Operator를 배포하고 설정합니다.
* [네트워크 규칙](https://docs.coreweave.com/ko/products/sandboxes/client/ref/networking/network-rules.md): `EgressRule` 및 `SelectorBlock`의 전체 레퍼런스입니다.
* [정책 레퍼런스](https://docs.coreweave.com/ko/products/sandboxes/reference/profile.md): 선언한 이그레스 규칙이 벗어나지 않아야 하는 정책 권한 범위를 설명합니다.
* [퍼블릭 엔드포인트](https://docs.coreweave.com/ko/products/sandboxes/public-endpoints.md): 샌드박스 내부의 서비스에 퍼블릭 주소를 부여합니다.
