인포레터에서 최신 DevOps 트렌드를 격주로 만나보세요!
Teleport를 사용하여 리소스 자동 등록
Teleport의 API를 사용하여 인프라의 리소스를 Teleport 클러스터에 자동으로 등록할 수 있습니다.
Teleport는 AWS, Azure 및 Google Cloud에서 Kubernetes 클러스터의 자동 검색을 이미 지원하며, Amazon EC2의 서버도 지원합니다. 다른 리소스 및 클라우드 제공자를 지원하려면 API를 사용하여 자체 워크플로를 작성할 수 있습니다.
이 가이드에서는 Teleport에 리소스를 자동으로 등록하는 데 사용할 수 있는 몇 가지 라이브러리를 보여줍니다. 여러분의 워크스테이션에서 로컬로 실행할 수 있는 예제를 사용하겠습니다.
자동 등록은 다음 단계로 구성됩니다:
- 서비스 검색 솔루션(예: Kubernetes API 서버, Consul 또는 클라우드 제공자의 API)에서 인프라의 리소스를 조회합니다.
- Teleport에 등록된 리소스를 조회하기 위해 Teleport gRPC API를 사용합니다.
- Teleport에 등록되지 않은 인프라의 리소스에 대해 Teleport API를 사용하여 새로운 Teleport 서비스를 시작하거나 기존 Teleport 서비스에 리소스를 등록합니다.
- Teleport에 등록되어 있지만 인프라에 없는 리소스에 대해서는 Teleport API를 사용하여 리소스를 등록 해제하고, 필요할 경우 해당 리소스를 프록시하는 Teleport 서비스를 제거합니다.
이 가이드에서 구축하는 프로그램은 학습 도구로 의도되었습니다. 프로덕션 Teleport 클러스터에 연결하지 마십시오. 대신 데모 클러스터를 사용하세요.
전제 조건
-
실행 중인 Teleport 클러스터 버전 17.0.0-dev 이상. Teleport를 시작하려면 가입하여 무료 평가판을 이용하거나 데모 환경 설정 방법을 확인하십시오.
-
tctl
관리자 도구와tsh
클라이언트 도구.tctl
및tsh
다운로드 방법에 대한 지침은 설치를 방문하십시오.
- 워크스테이션에 Docker가 설치되어 있어야 합니다. Docker 시작하기.
- 워크스테이션에 Go 버전 1.22 이상이 설치되어 있어야 합니다. Go 다운로드 페이지를 참조하세요. 이 가이드를 완료하기 위해 Go에 익숙할 필요는 없지만, 프로덕션 준비 완료 Teleport 클라이언트 애플리케이션을 빌드하려면 Go에 대한 지식이 필요합니다.
Tip
데모 프로젝트를 설정할 계획이 없더라도 이 가이드를 따라 Teleport 클러스터에 서비스를 자동으로 등록하는 데 사용할 수 있는 라이브러리, 유형 및 기능을 확인할 수 있습니다.
1/5단계. Go 프로젝트 설정
최소 API 클라이언트의 소스 코드를 다운로드합니다:
git clone https://github.com/gravitational/teleport -b branch/v17cd examples/service-discovery-api-client
이 가이드의 나머지 부분에서는 이 API 클라이언트를 설정하고 Teleport의 API를 사용하여 Teleport 애플리케이션 서비스 인스턴스를 외부 서비스 검색 솔루션과 동기화하는 방법을 보여줄 것입니다.
2/5단계. RBAC 리소스 정의
Teleport의 gRPC API와 통신하는 클라이언트는 Auth 서비스에 인증하기 위해 Teleport 사용자 및 역할의 정체성을 가정하며, 이 서비스는 특정 API 작업을 수행할 수 있도록 해당 정체성을 권한 부여합니다.
자신의 Teleport 사용자 또한 클라이언트를 위한 자격 증명을 생성하기 위해 API 클라이언트의 사용자로 가장할 권한이 필요합니다.
클라이언트 애플리케이션을 위한 사용자 및 역할 생성
우리 클라이언트 애플리케이션은 조인 토큰을 생성하고, 애플리케이션을 나열하며, Auth 서비스에 등록된 애플리케이션 서비스 인스턴스의 기록을 삭제하는 권한이 있는 사용자로 Teleport에 인증합니다. 사용자는 또한 애플리케이션을 나열하는 데 필요한 모든 레이블 (app_labels
)에 접근할 수 있는 권한을 가집니다.
클라이언트 애플리케이션에 적절한 권한을 가진 사용자 및 역할을 정의하려면 다음 내용을 register-apps.yaml
파일에 추가합니다:
kind: role
version: v5
metadata:
name: register-apps
spec:
allow:
app_labels:
"*": "*"
rules:
- resources: ["token"]
verbs: ["create"]
- resources: ["app"]
verbs: ["list"]
- resources: ["app_server"]
verbs: ["delete"]
---
kind: user
metadata:
name: register-apps
spec:
roles: ["register-apps"]
version: v2
사용자와 역할을 생성합니다:
tctl create -f register-apps.yamlrole 'register-apps' has been createduser "register-apps" has been created
클라이언트 애플리케이션의 임시 사용 활성화
모든 Teleport 사용자와 마찬가지로 Teleport Auth 서비스는 register-apps
사용자를 짧은 수명의 TLS 자격 증명을 발급하여 인증합니다. 이 경우, 우리는 register-apps
역할과 사용자를 가장하여 자격 증명을 수동으로 요청할 것입니다.
자체 호스팅된 Teleport Enterprise 배포를 실행 중이고 Auth 서비스 호스트에서 tctl
을 사용하고 있다면, 이미 가장 권한을 가지고 있을 것입니다.
register-apps
에 대한 가장 권한을 부여하려면 역할을 정의하는 register-apps-impersonator.yaml
이라는 파일을 생성합니다:
kind: role
version: v5
metadata:
name: register-apps-impersonator
spec:
allow:
impersonate:
roles:
- register-apps
users:
- register-apps
register-apps-impersonator
역할을 생성합니다:
tctl create -f register-apps-impersonator.yamlrole 'register-apps-impersonator'가 생성되었습니다.
register-apps-impersonator
역할을 Teleport 사용자에게 할당하려면, 인증 제공자에 맞는 적절한 명령어를 실행하십시오:
-
로컬 사용자의 역할을 쉼표로 구분된 목록으로 가져옵니다:
ROLES=$(tsh status -f json | jq -r '.active.roles | join(",")') -
새로운 역할을 추가하기 위해 로컬 사용자를 수정합니다:
tctl users update $(tsh status -f json | jq -r '.active.username') \ --set-roles "${ROLES?},register-apps-impersonator" -
Teleport 클러스터에서 로그아웃한 후 다시 로그인하여 새로운 역할을 가집니다.
-
텍스트 편집기에서
github
인증 커넥터를 엽니다:tctl edit github/github -
github
커넥터를 수정하여teams_to_roles
섹션에register-apps-impersonator
을 추가합니다.이 역할에 매핑해야 하는 팀은 조직의 역할 기반 액세스 제어(RBAC) 설계에 따라 다릅니다. 그러나 팀은 귀하의 사용자 계정을 포함해야 하며, 조직 내에서 가장 작은 팀이어야 합니다.
예시는 다음과 같습니다:
teams_to_roles: - organization: octocats team: admins roles: - access + - register-apps-impersonator
-
파일을 편집하고 저장하여 변경 사항을 적용합니다.
-
Teleport 클러스터에서 로그아웃한 후 다시 로그인하여 새로운 역할을 가집니다.
-
saml
구성 리소스를 가져옵니다:tctl get --with-secrets saml/mysaml > saml.yaml--with-secrets
플래그는spec.signing_key_pair.private_key
의 값을saml.yaml
파일에 추가합니다. 이 키는 민감한 값을 포함하므로, 리소스를 업데이트한 후 즉시saml.yaml
파일을 삭제해야 합니다. -
saml.yaml
을 수정하여attributes_to_roles
섹션에register-apps-impersonator
을 추가합니다.이 역할에 매핑해야 하는 속성은 조직의 역할 기반 액세스 제어(RBAC) 설계에 따라 다릅니다. 그러나 그룹은 귀하의 사용자 계정을 포함해야 하며, 조직 내에서 가장 작은 그룹이어야 합니다.
예시는 다음과 같습니다:
attributes_to_roles: - name: "groups" value: "my-group" roles: - access + - register-apps-impersonator
-
변경 사항을 적용합니다:
tctl create -f saml.yaml -
Teleport 클러스터에서 로그아웃한 후 다시 로그인하여 새로운 역할을 가집니다.
-
oidc
구성 리소스를 가져옵니다:tctl get oidc/myoidc --with-secrets > oidc.yaml--with-secrets
플래그는spec.signing_key_pair.private_key
의 값을oidc.yaml
파일에 추가합니다. 이 키는 민감한 값을 포함하므로, 리소스를 업데이트한 후 즉시oidc.yaml
파일을 삭제해야 합니다. -
oidc.yaml
을 수정하여claims_to_roles
섹션에register-apps-impersonator
을 추가합니다.이 역할에 매핑해야 하는 클레임은 조직의 역할 기반 액세스 제어(RBAC) 설계에 따라 다릅니다. 그러나 그룹은 귀하의 사용자 계정을 포함해야 하며, 조직 내에서 가장 작은 그룹이어야 합니다.
예시는 다음과 같습니다:
claims_to_roles: - name: "groups" value: "my-group" roles: - access + - register-apps-impersonator
-
변경 사항을 적용합니다:
tctl create -f oidc.yaml -
Teleport 클러스터에서 로그아웃한 후 다시 로그인하여 새로운 역할을 가집니다.
이제 register-apps
역할과 사용자를 위한 서명된 인증서를 생성할 수 있습니다.
3/5단계. 클라이언트 애플리케이션을 위한 아이덴티티 내보내기
모든 Teleport 사용자와 마찬가지로, register-apps
는 Teleport 클러스터에 연결하기 위해 서명된 자격 증명이 필요합니다. 이러한 자격 증명을 요청하기 위해 tctl auth sign
명령을 사용합니다.
다음 tctl auth sign
명령은 register-apps
사용자를 가장하여 서명된 자격 증명을 생성하고, 로컬 디렉토리에 아이덴티티 파일을 작성합니다:
tctl auth sign --user=register-apps --out=auth.pem
아이덴티티 파일 auth.pem
은 TLS와 SSH 자격 증명을 모두 포함합니다. 클라이언트 애플리케이션은 SSH 자격 증명을 사용하여 프록시 서비스에 연결하고, 프록시 서비스는 Auth 서비스에 대한 리버스 터널 연결을 설정합니다. 플러그인은 이 리버스 터널과 TLS 자격 증명을 사용하여 Auth 서비스의 gRPC 엔드포인트에 연결합니다.
4/5단계. 클라이언트 애플리케이션 작성하기
이 단계에서는 예제 클라이언트 애플리케이션을 안내합니다.
우리의 데모 애플리케이션은 인기 있는 오픈 소스 메시지 브로커인 RabbitMQ에서 실행 중인 컨테이너를 감시하여 Teleport에 관리 API를 등록(필요한 경우 해지)합니다. 구현 방법은 다음과 같습니다:
- Teleport에 등록된 RabbitMQ 관리 API 엔드포인트를 가져옵니다.
- RabbitMQ 컨테이너를 가져옵니다.
- RabbitMQ 컨테이너가 관리 API 엔드포인트와 일치하지 않으면, 컨테이너의 관리 API 엔드포인트를 Teleport에 등록하기 위해 조인 토큰을 생성하고 새로운 Teleport 애플리케이션 서비스 컨테이너를 실행합니다.
- Teleport에 등록된 RabbitMQ API 엔드포인트가 RabbitMQ 컨테이너와 일치하지 않으면, 해당 애플리케이션 서비스 컨테이너를 삭제하고 RabbitMQ API 엔드포인트의 등록을 해제합니다.
동적 등록
클라이언트 애플리케이션이 대상 애플리케이션의 모든 인스턴스를 프록시하기 위해 새로운 애플리케이션 서비스 인스턴스를 시작하지만, 동일한 애플리케이션 서비스 인스턴스를 통해 여러 애플리케이션을 프록시할 수도 있습니다.
이를 위해 클라이언트 애플리케이션을 Teleport API를 통해 애플리케이션을 동적으로 등록하도록 작성할 수 있습니다. 이 가이드에서 방법에 대해 논의할 것입니다.
Imports
examples/service-discovery-api-client/main.go
에서 찾을 수 있는 프로그램은 다음 패키지를 가져옵니다:
패키지 | 설명 |
---|---|
context | context.Context 유형을 포함합니다. context.Context 는 실패하거나 시간이 초과될 수 있는 외부 서비스에 대한 연결과 같은 장기 실행 루틴을 제어하는 추상화입니다. 프로그램은 컨텍스트를 취소하거나 타임아웃과 메타데이터를 할당할 수 있습니다. |
crypto/rand | 암호화 무작위화 함수를 포함하며, 이는 애플리케이션 서비스가 Teleport Auth 서비스와 신뢰 관계를 설정하는 데 사용할 조인 토큰을 생성하는 데 사용됩니다. |
encoding/hex | 우리가 crypto/rand 에서 사용하는 난수 생성기는 바이트 단위로 데이터를 반환하므로, 우리는 이 패키지를 사용하여 이를 16진수 문자열로 인코딩합니다. |
fmt | 데이터를 인쇄, 문자열 또는 오류로 형식화합니다. |
net | 네트워크 I/O를 처리합니다. |
net/url | URL을 구문 분석합니다. |
strings | 문자열을 조작합니다. |
time | 시간을 다룹니다. 이는 Auth 서비스에 연결하기 위한 타임아웃을 정의하고, 우리의 발견 로직을 루프에서 실행하기 위한 티커를 정의하는 데 사용됩니다. |
클라이언트는 다음과 같은 타사 코드를 가져옵니다:
패키지 | 설명 |
---|---|
github.com/docker/docker/api/types | Docker 데몬 API에서 사용되는 유형. 여기서 dtypes 로 별칭 처리됩니다. |
github.com/docker/docker/api/types/container | Docker 데몬 API에서 사용되는 컨테이너 특정 유형. |
github.com/docker/docker/api/types/filters | Docker 데몬 API에서 컨테이너 필터링에 사용되는 유형. |
github.com/docker/docker/api/types/strslice | Docker의 API 클라이언트 라이브러리에서 문자열 슬라이스 작업을 위한 유틸리티 패키지입니다. (Go에서 슬라이스는 배열과 유사하지만 가변 길이와 용량을 가집니다. 배열은 고정 크기를 가집니다.) |
github.com/docker/docker/client | 여기서 docker 로 별칭 처리되는 Docker API 클라이언트 라이브러리. |
github.com/gravitational/teleport/api/client | Auth 서비스의 gRPC API에 인증하고 요청을 수행하는 라이브러리로, 여기서 teleport 로 별칭 처리됩니다. |
github.com/gravitational/api/client/proto | Teleport의 프로토콜 버퍼 API 사양. |
github.com/gravitational/teleport/api/types | Auth 서비스 API에서 사용되는 유형, 예를 들어 애플리케이션 서비스 레코드. |
github.com/gravitational/trace | 표준 라이브러리보다 유용한 세부 정보를 가진 오류를 표시합니다. |
google.golang.org/grpc | gRPC 클라이언트 및 서버 라이브러리. |
전역 선언
프로그램은 나중에 구성 가능하도록 상수를 눈에 띄는 위치에 정의합니다:
const (
// proxyAddr를 Teleport Proxy Service 인스턴스의 호스트와 포트로 할당합니다
proxyAddr string = ""
teleportImage string = "public.ecr.aws/gravitational/teleport-distroless:13.3.7"
initTimeout = time.Duration(30) * time.Second
updateInterval = time.Duration(5) * time.Second
tokenTTL = time.Duration(5) * time.Minute
networkName string = "bridge"
managementPort string = "15672"
tokenLenBytes = 16
rabbitMQImage string = "rabbitmq:3-management"
)
우리는 프로그램에서 이러한 상수를 나중에 사용할 것입니다. 이들은 나중에 변경할 수 있는 몇 가지 값을 정의합니다:
상수 | 설명 |
---|---|
proxyAddr | Teleport Proxy Service의 호스트 및 포트, 예: mytenant.teleport.sh:443 , 이를 통해 클라이언트를 클러스터에 연결합니다. 이것을 여러분의 Proxy Service의 호스트와 포트로 할당하십시오. |
teleportImage | Teleport Application Service의 인스턴스를 실행하는 데 사용할 Teleport 컨테이너 이미지의 이름입니다. |
initTimeout | Teleport 클러스터에 연결하기 위한 타임아웃 (30초). |
updateInterval | Application Service 인스턴스와 애플리케이션 컨테이너를 조정하는 동안 프로그램이 기다리는 간격 (5초). |
tokenTTL | Application Service 인스턴스가 Teleport 클러스터와 신뢰를 구축하는 데 사용할 조인 토큰의 TTL을 설정하는 기간입니다. 이 클라이언트 애플리케이션은 토큰을 생성한 직후에 사용하므로, 이 TTL을 작은 값 (5분)으로 설정하여 자격 증명의 유출을 방지할 수 있습니다. |
networkName | 애플리케이션 컨테이너를 검색하기 위한 Docker 네트워크의 이름입니다. bridge 는 Docker 데몬이 관리하는 기본 로컬 네트워크입니다. |
managementPort | RabbitMQ 컨테이너의 관리 API 포트입니다. |
tokenLenBytes | 생성할 조인 토큰의 길이(바이트 단위)입니다. |
rabbitMQImage | RabbitMQ 컨테이너 이미지의 이름입니다. 여기서 사용하는 컨테이너 이미지는 관리 API가 활성화되어 있습니다. |
const
선언 아래에는 다음 타입 선언이 있습니다:
type tokenDemoApp struct {
dockerClient *docker.Client
teleportClient *teleport.Client
}
이것은 프로그램에서 선언할 유일한 타입입니다. 이 타입에는 Docker 데몬 API와 Teleport Auth Service API에 대한 클라이언트 포인터가 포함되어 있습니다. 이 프로그램은 클라이언트를 초기화한 다음 tokenDemoApp
의 메서드를 호출합니다.
등록된 애플리케이션의 관리 엔드포인트 URL 가져오기
우리 프로그램은 Teleport에 등록된 애플리케이션을 가져옵니다. 나중에 현재 실행 중인 애플리케이션 컨테이너와 이 애플리케이션을 비교할 것입니다.
Teleport는 등록된 리소스를 두 가지 방법으로 나타냅니다:
- 동적 리소스: 클러스터에 적용한 구성 문서입니다. 예:
app
,kube_cluster
, 및db
리소스. Teleport는 이러한 리소스를 프록시하기 위해 적절한 서비스의 인스턴스를 자동으로 찾습니다. - 서비스 인스턴스: 인프라에서 특정 리소스를 프록시하는 Teleport 서비스로, 서비스의 구성 파일에서 지정합니다.
우리 애플리케이션에서는 애플리케이션당 하나의 Application Service 인스턴스를 생성하므로, Application Service 인스턴스를 나열하고 이들이 프록시하는 리소스를 살펴보겠습니다. 다른 클라이언트 애플리케이션은 동적으로 등록된 리소스를 조회해야 할 수도 있습니다.
다음은 애플리케이션이 등록된 애플리케이션의 URL을 가져오는 데 사용하는 메서드입니다:
func (t *tokenDemoApp) listRegisteredAppURLs(ctx context.Context) (map[string]types.AppServer, error) {
m := make(map[string]types.AppServer)
for {
req := proto.ListResourcesRequest{
ResourceType: "app_server",
Limit: 10,
}
resp, err := t.teleportClient.ListResources(
ctx,
req,
)
if err != nil {
return nil, trace.Wrap(err)
}
for _, r := range resp.Resources {
if p, ok := r.(types.AppServer); ok {
m[p.GetApp().GetURI()] = p
}
}
// 요청할 페이지가 더 이상 없습니다
if resp.NextKey == "" {
break
}
req.StartKey = resp.NextKey
}
return m, nil
}
tokenDemoApp.teleportClient
는 Teleport의 API 라이브러리에서 *Client
타입입니다. *Client
타입의 ListResources
메서드는 proto.ListResourcesRequest
에 명시된 매개변수로 Teleport API에 리소스 목록을 쿼리합니다.
결과가 페이지로 나뉘어 있기 때문에 listRegisteredAppURLs
는 이 쿼리를 for
루프에서 실행합니다. 쿼리의 응답에 빈 NextKey
필드가 없으면 다음 페이지의 시작을 조회하기 위해 사용해야 하는 키를 나타냅니다. 그러면 함수는 다음 키를 사용하여 쿼리를 다시 실행합니다.
결국 이 프로그램은 두 개의 맵(즉, 해시 테이블)을 비교하여 등록된 애플리케이션과 애플리케이션 컨테이너를 조정하도록 확장할 것입니다:
- Teleport에 등록된 애플리케이션을 나타내는
types.AppServer
에 대한 URL 문자열 맵. - RabbitMQ 컨테이너의 관리 API 엔드포인트 URL키를 가진 맵.
listRegisteredAppURLs
함수는 ListResources
의 결과를 반복하여 첫 번째 맵을 생성하며, 각 리소스가 types.AppServer
인 경우, 그 애플리케이션의 프로시된 URL을 키로 맵 내에 삽입하고 해당 types.AppServer
를 값으로 할당합니다.
다음 섹션에서는 두 번째 맵을 생성하는 방법을 보여드리겠습니다.
ListResources
는 리소스를 가져오기 위한 범용 메서드로, 결과를 정렬하고 필터링할 수 있습니다. 클라이언트 애플리케이션의 필요에 따라 대신 리소스별 메서드를 고려할 수 있습니다.
예를 들어, 이 메서드는 동적으로 등록된 애플리케이션만 반환합니다:
func (c *Client) GetApps(ctx context.Context) ([]types.Application, error)
이 메서드는 Kubernetes 서비스 인스턴스를 반환합니다:
func (c *Client) GetKubernetesServers(ctx context.Context) ([]types.KubeServer, error)
일반적으로 *Client
메서드는 Get[A-Za-z]+
패턴을 따르며 동적으로 등록된 리소스를 가져오고, Get[A-Za-z]+(Servers|Services)
패턴을 따르는 메서드는 Teleport 서비스의 기록을 가져옵니다.
애플리케이션 컨테이너의 관리 엔드포인트 URL 가져오기
다음 함수는 RabbitMQ 컨테이너의 URL을 가져옵니다:
func (t *tokenDemoApp) listAppContainerURLs(ctx context.Context, image string) (map[string]struct{}, error) {
c, err := t.dockerClient.ContainerList(ctx, dtypes.ContainerListOptions{
Filters: filters.NewArgs(filters.KeyValuePair{
Key: "ancestor",
Value: image,
}),
})
if err != nil {
return nil, trace.Wrap(err)
}
l := make(map[string]struct{})
for _, r := range c {
b, ok := r.NetworkSettings.Networks[networkName]
// 선택한 네트워크에 연결되지 않았으므로 건너뜁니다.
if !ok {
continue
}
u, err := url.Parse("http://"+net.JoinHostPort(
b.IPAddress,
managementPort,
))
if err != nil {
return nil, trace.Wrap(err)
}
l[u.String()] = struct{}{}
}
return l, nil
}
이 함수는 tokenDemoApp.dockerClient
필드와 Docker API 클라이언트 라이브러리의 Client
유형을 사용하여 Docker 데몬에 요청을 보내 컨테이너를 나열합니다. dtypes.ContainerListOptions
구조체는 Docker 데몬에 image
매개변수에 지정한 이미지를 가진 컨테이너만 나열하도록 지시합니다.
Docker 데몬에서 반환된 각 컨테이너에 대해, 우리는 미리 정해진 네트워크(기본 브리지 네트워크) 내에서 컨테이너의 IP 주소를 조회합니다. 모든 RabbitMQ 컨테이너가 관리 포트를 열어둘 것이라는 것을 알고 있기 때문에, 컨테이너의 IP 주소와 관리 포트를 사용하여 URL을 조합하고 이 함수에서 반환할 맵에 삽입합니다.
이 함수가 반환하는 맵은 URL 문자열을 빈 구조체에 할당합니다. Go에서 빈 구조체는 메모리를 소비하지 않습니다. Go 프로그램은 해시 테이블의 값으로 빈 구조체를 자주 사용합니다. 프로그램은 값 없이도 상수 시간 내에 키를 검색할 수 있습니다.
새로운 애플리케이션 서비스 인스턴스를 위한 토큰 생성
등록된 애플리케이션의 맵과 애플리케이션 컨테이너의 맵을 재조정한 후, 우리는 다음을 수행해야 합니다:
- 등록되지 않은 애플리케이션 컨테이너를 프록시하기 위해 애플리케이션 서비스 인스턴스를 시작합니다.
- 더 이상 실행 중인 애플리케이션에 해당하지 않는 애플리케이션 서비스 인스턴스를 삭제합니다.
새로운 애플리케이션 서비스 인스턴스를 시작하기 위해, 우리는 조인 토큰을 생성할 것입니다. Teleport의 서비스는 Auth Service에 토큰을 제출하여 클러스터에 조인할 수 있습니다.
아래 코드는 새로운 애플리케이션 서비스 인스턴스를 위한 조인 토큰을 생성합니다:
func cryptoRandomHex(len int) (string, error) {
randomBytes := make([]byte, len)
if _, err := rand.Read(randomBytes); err != nil {
return "", trace.Wrap(err)
}
return hex.EncodeToString(randomBytes), nil
}
func (t *tokenDemoApp) createAppToken(ctx context.Context) (string, error) {
n, err := cryptoRandomHex(tokenLenBytes)
if err != nil {
return "", trace.Wrap(err)
}
tok, err := types.NewProvisionTokenFromSpec(
n,
time.Now().Add(tokenTTL),
types.ProvisionTokenSpecV2{
Roles: types.SystemRoles{types.RoleApp},
})
if err := t.teleportClient.CreateToken(ctx, tok); err != nil {
return "", trace.Wrap(err)
}
return n, nil
}
위 예제는 *client.Client.CreateToken
을 사용하여 리소스를 Teleport 클러스터에 조인하는 방법을 보여줍니다. 이미 Teleport 서비스의 인스턴스 (예: 애플리케이션 서비스)가 실행 중인 경우, *client.Client.CreateApp
와 유사한 방법을 사용하여 리소스를 클러스터에 동적으로 조인하는 것이 더 간단합니다.
애플리케이션을 동적으로 등록하기 위해, 애플리케이션을 프록시할 새로운 애플리케이션 서비스 인스턴스를 시작하는 대신 *clientClient.CreateApp
메서드를 사용하십시오:
func (c *Client) CreateApp(ctx context.Context, app types.Application) error
이 작업이 수행되려면, 애플리케이션 서비스의 인스턴스가 이미 실행 중이어야 합니다. 또한, API 클라이언트 애플리케이션의 Teleport 사용자는 다음 권한을 가지고 있어야 합니다:
spec:
allow:
rules:
- resources: ["app"]
verbs: ["create"]
다른 리소스에 대해서는 다음 메서드를 사용하십시오. 이는 create
동사를 가진 해당 역할 권한이 필요합니다:
리소스 | 메서드 | 역할 내 |
---|---|---|
데이터베이스 | *client.Client.CreateDatabase | db |
Kubernetes 클러스터 | *client.Client.CreateKubernetesCluster | kube_cluster |
Windows 바탕화면 | *client.Client.CreateWindowsDesktop | windows_desktop |
서버가 Teleport 클러스터에 조인하기 위해 Teleport 서비스를 실행해야 하므로, API 클라이언트는 토큰을 사용하여 서버를 등록할 수 있습니다.
cryptoRandomHex
는 Teleport 내부에서 정의된 함수의 복사본입니다. 이 함수는 crypto/rand
패키지를 사용하여 임의의 바이트를 생성한 다음, 이를 16진수 형식의 문자열로 변환하여 조인 토큰으로 사용할 수 있습니다. 조인 토큰을 생성하기 위해 안전한 암호화 기술을 사용할 수 있습니다.
*tokenDemoApp.createAppToken
은 cryptoRandomHex
를 호출하고 그 결과를 types.NewProvisionTokenFromSpec
를 호출하는 데 사용합니다. 이는 Teleport API에 요청을 보낼 때 사용할 조인 토큰의 사양을 반환합니다. 이를 t.teleportClient.CreateToken
을 통해 수행합니다.
이 경우, 우리는 토큰에 이전에 설정한 TTL과 types.RoleApp
역할을 할당합니다. 이것은 해당 토큰이 애플리케이션 서비스 인스턴스를 위한 것임을 Auth Service에 알립니다.
CreateToken
은 토큰 생성이 실패하면 오류를 반환합니다. 성공하면, 우리는 *tokenDemoApp.createAppToken
을 호출한 곳에 토큰을 반환하여 새로운 애플리케이션 서비스 인스턴스를 실행할 수 있습니다.
클라이언트 애플리케이션은 다음 함수를 호출하여 생성한 토큰을 조회할 수 있습니다:
func (c *Client) GetTokens(ctx context.Context) ([]types.ProvisionToken, error)
이렇게 하려면, 클라이언트 애플리케이션은 Teleport 역할에서 다음 권한을 가져야 합니다:
spec:
allow:
rules:
- resources: ["token"]
verbs: ["list", "read"]
이 가이드에서 보여주고 있는 애플리케이션에서는 애플리케이션이 이미 생성한 토큰을 알고 있기 때문에, 토큰을 조회할 필요가 없습니다.
애플리케이션 서비스 컨테이너 시작
우리 프로그램은 애플리케이션 서비스 인스턴스를 시작하는 방법이 필요합니다. 이를 위해, 우리가 이전에 생성한 토큰과 RabbitMQ 관리 API 엔드포인트의 URL을 사용하여 컨테이너를 시작합니다:
func (t *tokenDemoApp) startApplicationServiceContainer(
ctx context.Context,
token string,
u url.URL,
) error {
name := strings.ReplaceAll(u.Hostname(), ".", "-")
resp, err := t.dockerClient.ContainerCreate(
ctx,
&container.Config{
Image: teleportImage,
Entrypoint: strslice.StrSlice{
"/usr/bin/dumb-init",
"teleport",
"start",
"--roles=app",
"--auth-server=" + proxyAddr,
"--token=" + token,
"--app-name=rabbitmq-" + name,
"--app-uri=" + u.String(),
},
},
nil,
nil,
nil,
"",
)
if err != nil {
return trace.Wrap(err)
}
err = t.dockerClient.ContainerStart(
ctx,
resp.ID,
dtypes.ContainerStartOptions{},
)
if err != nil {
return trace.Wrap(err)
}
return nil
}
Teleport 애플리케이션 서비스는 트래픽을 당신의 Teleport 웹 UI 주소의 서브도메인으로 리디렉션하여 등록된 애플리케이션으로 보냅니다. 우리는 URL에 안전하면서도 다른 등록된 애플리케이션과 충돌하지 않는 애플리케이션 이름이 필요합니다. 이 경우, 우리는 RabbitMQ 컨테이너의 IP 주소를 사용하며, 점을 하이픈으로 교체합니다.
다음으로, 우리는 컨테이너를 생성합니다. 컨테이너의 진입점으로 사용되는 실행 파일은 기본적으로 teleport
이미지가 사용하는 것과 동일하지만, 컨테이너를 애플리케이션 서비스 인스턴스로 실행하고 RabbitMQ 컨테이너를 프록시로 설정하는 추가 플래그가 있습니다.
마지막으로, 우리가 생성한 컨테이너의 ID를 사용하여 컨테이너를 실행합니다.
애플리케이션 서비스 인스턴스 제거
애플리케이션 서비스 컨테이너를 생성하는 것과 함께, 우리의 클라이언트 애플리케이션은 실행 중인 컨테이너와 등록된 애플리케이션을 조정하기 위해 불필요한 애플리케이션 서비스 인스턴스를 제거합니다:
func (t *tokenDemoApp) pruneAppServiceInstance(ctx context.Context, p types.AppServer) error {
host := p.GetHostname()
if err := t.teleportClient.DeleteApplicationServer(
ctx,
p.GetNamespace(),
p.GetHostID(),
p.GetName(),
); err != nil {
return trace.Wrap(err)
}
fmt.Println("불필요한 애플리케이션 서비스 기록 삭제:", p.GetName())
// 컨테이너를 제거할 때는 오류를 확인하지 않습니다. 이미 제거되었을 수 있습니다.
t.dockerClient.ContainerStop(ctx, host, container.StopOptions{})
t.dockerClient.ContainerRemove(ctx, host, dtypes.ContainerRemoveOptions{})
fmt.Println("불필요한 애플리케이션 서비스 컨테이너 삭제:", host)
return nil
}
이 함수는 types.AppServer
를 가져와 이를 Teleport에서 등록 해제하고 관련된 애플리케이션 서비스 컨테이너를 제거합니다.
Teleport는 자동으로 오래된 애플리케이션 서비스 기록을 등록 해제하지만, 애플리케이션 서비스 인스턴스를 중지한 후에는 시간이 걸릴 수 있습니다.
리소스의 TTL 변경하기
Teleport가 백엔드에서 등록 해제하기 전에 리소스의 가용성을 확인하는 간격을 변경할 수 있습니다. 이렇게 하려면, types.Resource
인터페이스의 SetExpiry
메서드를 사용하십시오.
예를 들어, 다음 SetExpiry
호출은 Teleport가 리소스의 가용성을 확인할 때까지 10분 동안 유효한 WindowsDesktopV3
리소스를 구성합니다:
desktop.SetExpiry(time.Now().Add(10 * time.Minute))
애플리케이션 서비스 인스턴스를 수동으로 등록 해제하려면, pruneAppServiceInstance
함수의 p
매개변수를 사용하여 삭제할 애플리케이션 서비스 인스턴스의 네임스페이스, 호스트 ID 및 이름을 가져와서 *Client.DeleteApplicationServer
메서드를 호출합니다.
Teleport 네임스페이스는 더 이상 사용되지 않지만, 여전히 가끔 Teleport API 클라이언트 라이브러리에서 나타납니다. Teleport가 지원하는 유일한 네임스페이스는 default
라고 합니다.
다음으로, 이 함수는 types.AppServer
와 연관된 애플리케이션 서비스 컨테이너를 중지하고 제거합니다. 애플리케이션 서비스 기록의 호스트 이름은 애플리케이션 서비스 컨테이너의 ID와 같으므로, t.dockerClient.ContainerStop
및 t.dockerClient.ContainerRemove
에 호스트 이름을 전달할 수 있습니다.
등록된 애플리케이션과 애플리케이션 컨테이너 조정
우리는 Application Service 인스턴스와 애플리케이션 컨테이너를 나열, 추가 및 제거하는 여러 기능을 선언했습니다. 다음으로, 이러한 기능을 사용하여 조정 로직을 구현할 것입니다:
func (t *tokenDemoApp) reconcileApps() error {
ctx := context.Background()
apps, err := t.listRegisteredAppURLs(ctx)
if err != nil {
return trace.Wrap(err)
}
urls, err := t.listAppContainerURLs(ctx, rabbitMQImage)
if err != nil {
return trace.Wrap(err)
}
for u, _ := range urls {
if _, ok := apps[u]; ok {
continue
}
tok, err := t.createAppToken(ctx)
if err != nil {
return trace.Wrap(err)
}
fmt.Println("URL에 대한 새로운 애플리케이션 토큰이 생성되었습니다: " + u)
r, err := url.Parse(u)
if err != nil {
return trace.Wrap(err)
}
err = t.startApplicationServiceContainer(ctx, tok, *r)
if err != nil {
return trace.Wrap(err)
}
fmt.Println("URL을 프록시하기 위해 애플리케이션 서비스 컨테이너가 시작되었습니다: " + u)
}
for a, p := range apps {
_, ok := urls[a]
if ok {
continue
}
if err := t.pruneAppServiceInstance(ctx, p); err != nil {
return trace.Wrap(err)
}
}
return nil
}
*tokenDemoApp.reconcileApps
는 listAppContainerURLs
와 listRegisteredAppURLs
를 호출하여 등록된 애플리케이션과 실행 중인 애플리케이션 컨테이너의 맵을 생성하여 작동합니다. 그런 다음 각 맵의 키 내에서 URL을 반복하며, 한 맵 내에서 다른 맵의 존재 여부를 확인합니다.
애플리케이션 컨테이너의 맵 내에서 URL이 등록된 애플리케이션의 맵에 존재하지 않으면, 이는 하나의 애플리케이션이 아직 등록되지 않았음을 의미하므로, 우리는 토큰을 생성하고 Teleport 애플리케이션 서비스 인스턴스를 시작합니다.
등록된 애플리케이션의 맵 내에서 URL이 애플리케이션 컨테이너의 맵에 존재하지 않으면, 이는 불필요한 애플리케이션 서비스 인스턴스가 있음을 의미하며, 우리는 pruneAppServiceInstance
를 호출하여 이를 제거합니다.
클라이언트 초기화
*tokenDemoApp.reconcileApps
메서드는 단일 조정을 수행합니다. 다음 단계는 API 클라이언트를 초기화하여 프로그램의 진입점 내에서 조정을 루프에서 실행할 수 있도록 하는 것입니다:
func newTokenDemoApp() *tokenDemoApp {
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, initTimeout)
defer cancel()
creds := teleport.LoadIdentityFile("auth.pem")
t, err := teleport.New(ctx, teleport.Config{
Addrs: []string{proxyAddr},
Credentials: []teleport.Credentials{creds},
})
if err != nil {
panic(err)
}
fmt.Println("Teleport에 연결되었습니다.")
d, err := docker.NewClientWithOpts(
docker.WithAPIVersionNegotiation(),
)
if err != nil {
panic(err)
}
fmt.Println("Docker 데몬에 연결되었습니다.")
return &tokenDemoApp{
teleportClient: t,
dockerClient: d,
}
}
client
는 여기서 teleport
로 별칭을 붙이며, Teleport의 API 클라이언트를 설정하는 라이브러리입니다. 우리의 플러그인은 client.LoadIdentityFile
을 호출하여 client.Credentials
를 가져옴으로써 Teleport 클라이언트를 초기화합니다. 그런 다음 client.Credentials
를 사용하여 client.New
를 호출하여 제공된 아이덴티티 파일을 사용하여 Addrs
필드에 지정된 Teleport Proxy Service에 연결합니다.
Warning
이 프로그램은 인증 정보나 Teleport 클러스터 주소를 검증하지 않습니다. 다음 사항을 확인하십시오:
- 이전에 내보낸 아이덴티티 파일의 TTL이 만료되지 않았습니다.
teleport.Config
의Addrs
필드에 제공한 값은 Teleport Proxy Service의 호스트 및 웹 포트를 포함합니다. 예:mytenant.teleport.sh:443
newTokenDemoApp
함수는 또한 Docker 클라이언트를 초기화합니다. 이는 버전 협상(docker.WithAPIVersionNegotiation
)을 사용하여 클라이언트의 API 버전과 Docker 데몬의 불일치로 인한 오류를 피합니다.
진입점
우리는 진입점 함수 main
으로 애플리케이션을 연결합니다:
func main() {
fmt.Println("애플리케이션 시작 중")
app := newTokenDemoApp()
k := time.NewTicker(updateInterval)
defer k.Stop()
for {
<-k.C
if err := app.reconcileApps(); err != nil {
panic(err)
}
}
}
main
함수는 API 클라이언트를 초기화하기 위해 newTokenDemoApp
을 호출합니다. 그런 다음 time.NewTicker
를 호출하여 진입점이 조정 루틴을 실행하는 데 사용할 Go 채널을 반환합니다. 채널은 병행 루틴 간의 통신을 관리하는 Go 원시 데이터입니다.
우리가 티커를 생성한 후, 진입점은 updateInterval
이 경과할 때마다 티커의 채널(k.C
)에서 수신합니다. 채널에서 수신할 때까지 차단되며, 매 간격마다 app.reconcileApps
를 호출합니다.
5/5단계. 클라이언트 애플리케이션 테스트
클라이언트 애플리케이션을 실행하여 Teleport가 인프라에서 리소스를 등록하고 등록 해제하여 서비스 발견 솔루션과 동기화하는 방법을 확인하세요.
애플리케이션이 Teleport 컨테이너 이미지를 기반으로 컨테이너를 생성할 수 있도록 Teleport 컨테이너 이미지를 끌어왔는지 확인하세요:
docker image pull public.ecr.aws/gravitational/teleport-distroless:13.3.7
프로젝트 디렉토리에서 다음 명령을 실행합니다:
go run main.go애플리케이션 시작 중Teleport에 연결됨Docker 데몬에 연결됨
새 터미널에서 세 개의 RabbitMQ 컨테이너를 실행합니다:
for i in {1..3}; do docker run -d rabbitmq:3-management; done;
애플리케이션을 실행한 터미널에서는 다음과 유사한 출력이 표시되어야 합니다:
새로운 애플리케이션 토큰이 URL에 대해 생성되었습니다: http://172.17.0.4:15672
URL에 대한 프록시로 Application Service 컨테이너가 시작되었습니다: http://172.17.0.4:15672
새로운 애플리케이션 토큰이 URL에 대해 생성되었습니다: http://172.17.0.3:15672
URL에 대한 프록시로 Application Service 컨테이너가 시작되었습니다: http://172.17.0.3:15672
새로운 애플리케이션 토큰이 URL에 대해 생성되었습니다: http://172.17.0.2:15672
URL에 대한 프록시로 Application Service 컨테이너가 시작되었습니다: http://172.17.0.2:15672
RabbitMQ 인스턴스가 Teleport에 등록되었는지 확인합니다:
tsh apps ls애플리케이션 설명 유형 공개 주소 레이블------------------- -------------------- ---- ------------------------ -------------------rabbitmq-172-17-0-2 HTTP rabbitmq-172-17-0-2.3... teleport.dev/originrabbitmq-172-17-0-3 HTTP rabbitmq-172-17-0-3.3... teleport.dev/originrabbitmq-172-17-0-4 HTTP rabbitmq-172-17-0-4.3... teleport.dev/origin
다음으로, RabbitMQ 컨테이너 중 하나를 중지합니다:
docker stop $(docker ps --filter "ancestor=rabbitmq:3-management" -q --last 1)
애플리케이션을 실행한 터미널에서는 다음과 유사한 출력이 표시되어야 합니다:
불필요한 Application Service 레코드 삭제: rabbitmq-172-17-0-4
불필요한 Application Service 컨테이너 삭제: 63facaa3033a
tsh apps ls
를 실행하면 이제 두 개의 등록된 애플리케이션이 표시됩니다.
다음 단계
우리는 실행 중인 Docker 컨테이너와 등록된 애플리케이션을 최신 상태로 유지하는 Teleport API 클라이언트를 구현했습니다. Teleport의 API를 사용하여 등록된 Teleport 리소스를 자동으로 서비스 발견 솔루션과 동기화할 수 있습니다.
예제 참조
Teleport는 자체 자동 리소스 검색 솔루션인 Teleport Discovery Service를 포함하고 있으며, Teleport가 어떻게 검색 로직을 구현하는지 보려면 소스 코드를 참조하십시오.
Teleport 코드 저장소에는 생산 준비가 완료된 Teleport API 클라이언트 예제가 포함되어 있습니다. 현재 리소스를 자동으로 검색하는 플러그인은 유지 관리하지 않지만, 이러한 예제를 사용하여 구성 구문 분석, 재시도 및 기타 작업을 구현하는 방법을 확인할 수 있습니다.
단기 자격 증명으로 클라이언트 애플리케이션 프로비저닝
이 예제에서는 tctl auth sign
명령을 사용하여 작성한 프로그램에 대한 자격 증명을 가져왔습니다. 생산 환경에서는 이러한 자격 증명이 도난될 위험을 줄이기 위해 머신 ID를 통해 단기 자격 증명을 프로비저닝할 것을 권장합니다. 자세한 내용은 머신 ID 문서를 참조하십시오.