Infograb logo
Teleport를 사용하여 리소스를 자동으로 등록하기

Teleport의 API를 사용하여 인프라에 리소스를 자동으로 등록할 수 있습니다.

Teleport는 AWS, Azure 및 Google Cloud에서 Kubernetes 클러스터와 Amazon EC2의 서버의 자동 발견을 이미 지원합니다. 다른 리소스와 클라우드 공급자를 지원하려면 API를 사용하여 자체 워크플로를 작성할 수 있습니다.

이번 가이드에서는 Teleport와 함께 리소스를 자동으로 등록하는 데 사용할 수 있는 몇 가지 라이브러리를 시연할 것입니다. 여러분의 워크스테이션에서 로컬로 실행할 수 있는 예제를 사용할 것입니다.

자동 등록은 다음 단계로 구성됩니다:

  • 서비스 검색 솔루션(예: Kubernetes API 서버, Consul 또는 클라우드 공급자의 API)에서 인프라 내 리소스를 조회합니다.
  • Teleport의 gRPC API를 사용하여 Teleport에 등록된 리소스를 조회합니다.
  • Teleport에 등록되지 않은 인프라 내의 모든 리소스에 대해 Teleport API를 사용하여 새로운 Teleport 서비스를 시작하거나 기존 Teleport 서비스에 리소스를 등록합니다.
  • Teleport에 등록되어 있으나 인프라에 없는 모든 리소스에 대해 Teleport API를 사용하여 리소스를 등록 해제하고, 필요할 경우 리소스를 프록시하는 Teleport 서비스를 제거합니다.

이번 가이드에서 구축하는 프로그램은 학습 도구로 설계되었습니다. 생산 Teleport 클러스터에 연결하지 마세요. 대신 데모 클러스터를 사용하세요.

필수 조건

  • 실행 중인 Teleport 클러스터 버전 이상. Teleport를 시작하려면, 가입하기 위해 무료 평가판에 등록하거나 데모 환경 설정하기를 참조하세요.

  • tctl 관리 도구와 tsh 클라이언트 도구.

    tctltsh 다운로드에 대한 지침은 설치를 방문하세요.

  • 워크스테이션에 Docker가 설치되어 있어야 합니다. Docker 시작하기.
  • 워크스테이션에 Go 버전 1.22 이상이 설치되어 있어야 합니다. Go 다운로드 페이지를 참고하세요. 이 가이드를 완료하기 위해 Go를 잘 알고 있어야 할 필요는 없지만, 프로덕션 준비가 완료된 Teleport 클라이언트 애플리케이션을 빌드하려면 Go 지식이 필요합니다.
Tip

데모 프로젝트를 설정할 계획이 없다 하더라도 이 가이드를 통해 Teleport 클러스터와 서비스를 자동으로 등록하기 위해 사용할 수 있는 라이브러리, 유형 및 기능을 확인할 수 있습니다.

1단계/5. Go 프로젝트 설정하기

최소 API 클라이언트의 소스 코드를 다운로드합니다:

git clone https://github.com/gravitational/teleport -b branch/v16
cd examples/service-discovery-api-client

가이드의 나머지 부분에서는 이 API 클라이언트를 설정하고 Teleport API를 사용하여 Teleport 애플리케이션 서비스 인스턴스를 외부 서비스 검색 솔루션과 동기화하는 방법을 보여줄 것입니다.

2단계/5. RBAC 리소스 정의하기

Teleport의 gRPC API와 통신하는 클라이언트는 인증을 위해 Teleport 사용자 및 역할의 신원을 가정하며, Auth 서비스는 특정 API 작업을 수행할 수 있도록 신원을 승인합니다.

고유한 Teleport 사용자도 클라이언트의 사용자로 위장할 수 있는 권한이 필요하여 클라이언트를 위한 자격 증명을 생성할 수 있습니다.

클라이언트 애플리케이션을 위한 사용자 및 역할 만들기

우리의 클라이언트 애플리케이션은 참여 토큰을 생성하고 애플리케이션을 나열하며 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.yaml
role 'register-apps' has been createduser "register-apps" has been created

클라이언트 애플리케이션의 위장 활성화

모든 Teleport 사용자와 마찬가지로 Teleport Auth 서비스는 register-apps 사용자를 짧은 수명의 TLS 자격 증명을 발급하여 인증합니다. 이 경우, 우리는 역할과 사용자를 위장하여 자격 증명을 수동으로 요청할 것입니다.

자체 호스팅된 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.yaml
role 'register-apps-impersonator' has been created

register-apps-impersonator 역할을 Teleport 사용자에게 할당하려면 인증 제공자에 맞는 적절한 명령어를 실행하세요:

  1. 로컬 사용자의 역할을 콤마로 구분된 목록으로 가져옵니다:

    ROLES=$(tsh status -f json | jq -r '.active.roles | join(",")')
  2. 로컬 사용자를 편집하여 새로운 역할을 추가합니다:

    tctl users update $(tsh status -f json | jq -r '.active.username') \ --set-roles "${ROLES?},register-apps-impersonator"
  3. Teleport 클러스터에서 로그아웃한 후 새로운 역할을 asum 하기 위해 다시 로그인합니다.

  1. github 인증 커넥터를 가져옵니다:

    tctl get github/github --with-secrets > github.yaml

    --with-secrets 플래그는 spec.signing_key_pair.private_key의 값을 github.yaml 파일에 추가합니다. 이 키는 민감한 값을 포함하고 있기 때문에, 리소스를 업데이트한 후 즉시 github.yaml 파일을 제거해야 합니다.

  2. github.yaml을 편집하고 teams_to_roles 섹션에 register-apps-impersonator을 추가합니다.

    이 역할에 매핑할 팀은 귀하의 조직에서 어떻게 역할 기반 접근 제어(RBAC)를 설계했느냐에 따라 달라집니다. 하지만 팀에는 귀하의 사용자 계정이 포함되어야 하며, 조직 내에서 가능한 한 작은 팀이어야 합니다.

    여기에 예시가 있습니다:

      teams_to_roles:
        - organization: octocats
          team: admins
          roles:
            - access
    +       - register-apps-impersonator
    
  3. 변경 사항을 적용합니다:

    tctl create -f github.yaml
  4. Teleport 클러스터에서 로그아웃한 후 새로운 역할을 assum 하기 위해 다시 로그인합니다.

  1. saml 구성 리소스를 가져옵니다:

    tctl get --with-secrets saml/mysaml > saml.yaml

    --with-secrets 플래그는 spec.signing_key_pair.private_key의 값을 saml.yaml 파일에 추가합니다. 이 키는 민감한 값을 포함하고 있기 때문에, 리소스를 업데이트한 후 즉시 saml.yaml 파일을 제거해야 합니다.

  2. saml.yaml을 편집하고 attributes_to_roles 섹션에 register-apps-impersonator을 추가합니다.

    이 역할에 매핑할 속성은 귀하의 조직에서 어떻게 역할 기반 접근 제어(RBAC)를 설계했느냐에 따라 달라집니다. 그러나 그룹에는 귀하의 사용자 계정이 포함되어야 하며, 조직 내에서 가능한 한 작은 그룹이어야 합니다.

    여기에 예시가 있습니다:

      attributes_to_roles:
        - name: "groups"
          value: "my-group"
          roles:
            - access
    +       - register-apps-impersonator
    
  3. 변경 사항을 적용합니다:

    tctl create -f saml.yaml
  4. Teleport 클러스터에서 로그아웃한 후 새로운 역할을 asum 하기 위해 다시 로그인합니다.

  1. oidc 구성 리소스를 가져옵니다:

    tctl get oidc/myoidc --with-secrets > oidc.yaml

    --with-secrets 플래그는 spec.signing_key_pair.private_key의 값을 oidc.yaml 파일에 추가합니다. 이 키는 민감한 값을 포함하고 있기 때문에, 리소스를 업데이트한 후 즉시 oidc.yaml 파일을 제거해야 합니다.

  2. oidc.yaml을 편집하고 claims_to_roles 섹션에 register-apps-impersonator을 추가합니다.

    이 역할에 매핑할 클레임은 귀하의 조직에서 어떻게 역할 기반 접근 제어(RBAC)를 설계했느냐에 따라 달라집니다. 그러나 그룹에는 귀하의 사용자 계정이 포함되어야 하며, 조직 내에서 가능한 한 작은 그룹이어야 합니다.

    여기에 예시가 있습니다:

      claims_to_roles:
        - name: "groups"
          value: "my-group"
          roles:
            - access
    +       - register-apps-impersonator
    
  3. 변경 사항을 적용합니다:

    tctl create -f oidc.yaml
  4. Teleport 클러스터에서 로그아웃한 후 새로운 역할을 asum 하기 위해 다시 로그인합니다.

이제 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를 통해 애플리케이션을 동적으로 등록하도록 할 수 있습니다. 이 가이드에서는 그렇게 하는 방법에 대해 논의할 것입니다.

임포트

examples/service-discovery-api-client/main.go에서 찾을 수 있는 프로그램은 다음 패키지를 임포트합니다:

패키지설명
contextcontext.Context 유형을 포함합니다. context.Context는 실패하거나 시간 초과가 발생할 수 있는 외부 서비스로의 연결과 같은 장기 실행 루틴을 제어하기 위한 추상화입니다. 프로그램은 컨텍스트를 취소하거나 시간 초과 및 메타데이터를 할당할 수 있습니다.
crypto/rand애플리케이션 서비스가 Teleport Auth 서비스와 신뢰를 확립하기 위해 사용할 참여 토큰을 생성하는 데 사용할 암호화 랜덤화 기능을 포함합니다.
encoding/hexcrypto/rand에서 사용하는 난수 생성기가 바이트로 데이터를 반환하므로 이 패키지를 사용하여 16진수 문자열로 인코딩합니다.
fmt데이터를 출력, 문자열 또는 오류로 포맷팅합니다.
net네트워크 I/O를 처리합니다.
net/urlURL을 파싱합니다.
strings문자열을 조작합니다.
time시간을 다룹니다. 이 기능은 Auth 서비스에 연결하는 데 대한 시간 초과와 동시에 우리의 검색 로직을 루프에서 실행하기 위한 티커를 정의하는 데 사용됩니다.

클라이언트는 다음과 같은 타사 코드를 가져옵니다:

패키지설명
github.com/docker/docker/api/typesDocker 데몬 API에서 사용되는 유형입니다. 여기서는 dtypes로 별칭을 붙였습니다.
github.com/docker/docker/api/types/containerDocker 데몬 API에서 사용되는 컨테이너 전용 유형입니다.
github.com/docker/docker/api/types/filtersDocker 데몬 API에서 컨테이너를 필터링하는 데 사용되는 유형입니다.
github.com/docker/docker/api/types/strsliceDocker의 API 클라이언트 라이브러리에서 문자열 슬라이스 작업을 위한 유틸리티 패키지입니다. (Go에서 슬라이스는 비슷한 배열이지만 가변 길이와 용량을 갖습니다. 배열은 고정된 크기를 갖습니다.)
github.com/docker/docker/client여기서 docker로 별칭을 붙인 Docker API 클라이언트 라이브러리입니다.
github.com/gravitational/teleport/api/clientAuth 서비스의 gRPC API에 인증하고 요청을 하는 라이브러리로, 여기서는 teleport로 별칭을 붙입니다.
github.com/gravitational/api/client/protoTeleport의 프로토콜 버퍼 API 사양입니다.
github.com/gravitational/teleport/api/typesAuth 서비스 API에서 사용되는 유형, 예를 들어 애플리케이션 서비스 기록입니다.
github.com/gravitational/trace표준 라이브러리보다 더 유용한 세부 정보로 오류를 표시합니다.
google.golang.org/grpcgRPC 클라이언트 및 서버 라이브러리입니다.

전역 선언

프로그램은 나중에 쉽게 구성할 수 있도록 하여 잘 보이는 곳에 상수를 정의합니다:

const (
	// proxyAddr에 Teleport 프록시 서비스 인스턴스의 호스트 및 포트를 할당합니다.
	proxyAddr      string = ""
	teleportImage  string = "public.ecr.aws/gravitational/teleport-distroless:16.2.0"
	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"
)

이 상수들은 나중에 프로그램에서 사용할 것입니다. 이들은 우리가 나중에 변경하고 싶을 수 있는 몇 가지 값을 정의합니다:

상수설명
proxyAddrTeleport 프록시 서비스의 호스트 및 포트, 예: mytenant.teleport.sh:443, 이는 클라이언트를 클러스터에 연결하는 데 사용됩니다. 자신의 프록시 서비스의 호스트 및 포트로 할당하세요.
teleportImageTeleport 애플리케이션 서비스의 인스턴스를 실행하는 데 사용할 Teleport 컨테이너 이미지의 이름입니다.
initTimeoutTeleport 클러스터에 연결하기 위한 시간초과(30초)입니다.
updateInterval애플리케이션 서비스 인스턴스와 애플리케이션 컨테이너를 연합하는 사이에 프로그램이 기다리는 시간(5초)입니다.
tokenTTL애플리케이션 서비스 인스턴스가 Teleport 클러스터와 신뢰를 확립하는 데 사용될 참여 토큰의 TTL을 설정하는 기간입니다. 이 클라이언트 애플리케이션은 생성 후 즉시 참여 토큰을 사용할 것이므로, 이 TTL을 작게(5분) 설정하여 이 자격 증명이 유출되는 것을 방지할 수 있습니다.
networkName애플리케이션 컨테이너를 검색하기 위한 Docker 네트워크의 이름입니다. bridge는 Docker 데몬에서 관리하는 기본 로컬 네트워크입니다.
managementPortRabbitMQ 컨테이너의 관리 API 포트입니다.
tokenLenBytes바이트 단위로 생성할 참여 토큰 길이입니다.
rabbitMQImageRabbitMQ 컨테이너 이미지의 이름입니다. 여기에서 사용하는 컨테이너 이미지는 관리 API가 활성화된 이미지입니다.

const 선언 아래에는 다음과 같은 유형 선언이 있습니다:

type tokenDemoApp struct {
	dockerClient   *docker.Client
	teleportClient *teleport.Client
}

이 구조체는 프로그램에서 선언하는 유일한 유형입니다. Docker 데몬 API와 Teleport Auth 서비스 API의 클라이언트에 대한 포인터를 포함합니다. 이 프로그램은 클라이언트를 초기화하고 그 다음에 tokenDemoApp의 메서드를 호출합니다.

등록된 애플리케이션의 관리 엔드포인트 URL 가져오기

우리 프로그램은 Teleport에 등록된 애플리케이션을 가져옵니다. 이후에는 현재 실행 중인 애플리케이션 컨테이너와 비교할 것입니다.

Teleport는 등록된 리소스를 두 가지 방법으로 나타냅니다:

  • 동적 리소스: 이는 클러스터에 대한 구성 문서로, 예를 들어 app, kube_clusterdb 리소스 등입니다. Teleport는 이러한 리소스를 프록시할 적절한 서비스 인스턴스를 자동으로 찾습니다.
  • 서비스 인스턴스: 이는 인프라에서 특정 리소스를 프록시하는 Teleport 서비스로, 서비스의 구성 파일에 지정합니다.

우리의 애플리케이션에서는 앱당 하나의 애플리케이션 서비스 인스턴스를 생성하고 있으므로 애플리케이션 서비스 인스턴스를 나열하고 그것들이 프록시하는 리소스를 조사할 것입니다. 다른 클라이언트 애플리케이션은 동적으로 등록된 리소스를 조회해야 할 수도 있습니다.

다음은 애플리케이션이 등록된 애플리케이션의 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 필드가 있는 동안 쿼리를 다시 실행합니다.

결국, 우리는 등록된 애플리케이션과 애플리케이션 컨테이너를 비교하여 비어 있지 않은 두 개의 맵(즉, 해시 테이블)을 만들도록 프로그램을 확장할 것입니다:

  • URL 문자열에서 types.AppServer에 대한 맵은 Teleport에 등록된 애플리케이션을 나타냅니다.
  • 관리 API 엔드포인트의 URL 키가 있는 맵은 RabbitMQ 컨테이너에 대한 것입니다.

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 데몬에 요청하여 컨테이너를 목록화합니다. dtypes.ContainerListOptions 구조체는 Docker 데몬에 특정 이미지가 있는 컨테이너만 목록화하도록 지시합니다.

docker 데몬으로부터 반환된 모든 컨테이너에 대해 미리 정해진 네트워크 내에서 컨테이너의 IP 주소를 조회합니다(모든 RabbitMQ 컨테이너에는 관리 포트가 열려 있다고 가정합니다). 우리는 컨테이너의 IP 주소와 관리 포트를 사용하여 URL을 구성하고 이 URL을 반환할 맵에 추가합니다.

반환되는 맵은 URL 문자열에 빈 구조체를 할당합니다. Go에서 빈 구조체는 메모리를 소비하지 않습니다. Go 프로그램은 빈 구조체를 해시 테이블 값으로 자주 사용하며, 이를 통해 프로그램은 값을 사용하지 않고도 키를 상수 시간에 검색할 수 있습니다.

새로운 애플리케이션 서비스 인스턴스를 위한 토큰 생성하기

등록된 애플리케이션과 애플리케이션 컨테이너의 맵을 일치시킨 후, 우리는:

  • 등록되지 않은 애플리케이션 컨테이너를 프록시하기 위해 애플리케이션 서비스 인스턴스를 시작합니다.
  • 실행 중이지 않은 애플리케이션에 해당하는 애플리케이션 서비스 인스턴스를 삭제합니다.

새로운 애플리케이션 서비스 인스턴스를 시작하려면 참여 토큰을 생성해야 합니다. Teleport의 서비스는 Auth 서비스에 토큰을 제시하여 클러스터에 참여할 수 있습니다.

아래 코드는 새로운 애플리케이션 서비스 인스턴스를 위한 참여 토큰을 생성합니다:

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과 유사한 메서드를 사용하여 클러스터에 동적으로 리소스를 가입하는 것이 더 간단합니다.

클라이언트 애플리케이션은 다음 기능을 호출하여 생성한 토큰을 조회할 수 있습니다:

func (c *Client) GetTokens(ctx context.Context) ([]types.ProvisionToken, error)

이를 위해 클라이언트 애플리케이션은 다음과 같은 권한이 필요합니다:

spec:
  allow:
    rules:
      - resources: ['token']
        verbs: ['list', 'read']

우리가 이 가이드에서 보여주는 애플리케이션에서는 애플리케이션이 생성한 토큰을 이미 알고 있으므로, 토큰을 조회할 필요는 없습니다.

cryptoRandomHex는 Teleport에서 내부적으로 정의된 함수의 복사본입니다. 이 함수는 crypto/rand 패키지를 사용하여 임의의 바이트를 생성한 후, 이를 16진수 형식의 문자열로 변환하여 참여 토큰에 사용할 것입니다. 참여 토큰을 생성하기 위해서는 보안 암호화 기술을 자유롭게 사용할 수 있습니다.

*tokenDemoApp.createAppTokencryptoRandomHex를 호출하며, 요청에 대해 types.NewProvisionTokenFromSpec을 호출하여 결과로 얻은 토큰으로 사용합니다. 이 토큰은 Teleport API에 요청할 때 사용할 참여 토큰 사양을 반환합니다.

이 경우에는 설정한 TTL에 types.RoleApp 역할을 추가하여 인증 서비스에게 해당 토큰이 애플리케이션 서비스 인스턴스를 위한 것이라고 알립니다.

CreateToken은 토큰 생성에 실패할 경우 오류를 반환합니다. 성공할 경우, 생성된 토큰을 호출자에게 반환하여 새로운 애플리케이션 서비스 인스턴스를 실행할 수 있습니다.

클라이언트 애플리케이션은 생성한 토큰을 조회하기 위해 다음 기능을 호출할 수 있습니다:

func (c *Client) GetTokens(ctx context.Context) ([]types.ProvisionToken, error)

클라이언트 애플리케이션은 조회할 때 다음과 같은 권한이 필요합니다:

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))

애플리케이션 서비스 인스턴스를 수동으로 등록 해제하기 위해 *Client.DeleteApplicationServer 메서드를 호출하며, pruneAppServiceInstance 함수의 p 파라미터를 사용하여 삭제할 애플리케이션 서비스 인스턴스의 네임스페이스, 호스트 ID 및 이름을 가져옵니다.

Teleport 네임스페이스는 더 이상 사용되지 않지만, 때때로 Teleport API 클라이언트 라이브러리에서 나타납니다. Teleport가 지원하는 유일한 네임스페이스는 default입니다.

다음으로, 이 함수는 types.AppServer와 연결된 애플리케이션 서비스 컨테이너를 정지하고 제거합니다. 애플리케이션 서비스 기록의 호스트 이름은 애플리케이션 서비스 컨테이너의 ID와 동일하므로, 이를 t.dockerClient.ContainerStopt.dockerClient.ContainerRemove에 전달할 수 있습니다.

등록된 애플리케이션과 애플리케이션 컨테이너 동기화하기

우리는 여러 기능을 선언하여 애플리케이션 서비스 인스턴스를 나열하고 추가 및 제거했습니다. 다음으로 이러한 기능을 사용하여 일치하는 논리를 구현할 것입니다:

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.reconcileAppslistAppContainerURLslistRegisteredAppURLs를 호출하여 등록된 애플리케이션 및 실행 중인 애플리케이션 컨테이너의 맵을 생성합니다. 그런 다음 각 맵의 키 내에서 URL을 반복하여 각 맵 내에서 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에서 API 클라이언트를 설정하는 라이브러리로 사용하는 것은 teleport입니다. 우리의 플러그인은 client.LoadIdentityFile을 호출하여 client.Credentials를 얻어 Teleport 클라이언트를 초기화합니다. 그런 다음, 이 client.Credentials를 사용하여 client.New를 호출하여 제공된 신원 파일을 사용하여 Addrs 필드에 지정된 Teleport 프록시 서비스에 연결합니다.

Warning

이 프로그램은 귀하의 자격 증명 또는 Teleport 클러스터 주소를 검증하지 않습니다. 다음 사항을 확인하세요:

  • 이전에 내보낸 신원 파일의 TTL이 만료되지 않았습니다.
  • teleport.ConfigAddrs 필드에 제공한 값에는 Teleport 프록시 서비스의 호스트와 웹 포트가 모두 포함되어 있습니다, 예: mytenant.teleport.sh:443.

newTokenDemoApp 함수는 또한 Docker 클라이언트를 초기화합니다. 이는 클라이언트의 API 버전과 Docker 데몬의 버전 불일치로 인한 오류를 피하기 위해 버전 협상을 사용합니다(docker.WithAPIVersionNegotiation).

진입점

프로그램을 결합하는 진입점 함수인 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 함수는 newTokenDemoApp을 호출하여 API 클라이언트를 초기화합니다. 그 다음에는 time.NewTicker를 호출하여 애플리케이션이 사용할 채널을 반환합니다. 이 채널은 여러 동시 루틴 간의 통신을 관리하는 Go 기본 구조입니다.

티커를 만든 후, 진입점은 티커의 채널(k.C)로부터 수신할 때마다, 주기적으로 updateInterval이 경과할 때까지 차단되며, 매 주기마다 app.reconcileApps를 호출합니다.

5단계/5. 클라이언트 애플리케이션 테스트하기

클라이언트 애플리케이션을 실행하여 Teleport가 리소스를 인프라와 서비스 검색 솔루션과 동기화하는 방법을 확인하세요.

애플리케이션이 사용할 수 있도록 Teleport 컨테이너 이미지를 미리 풀링했는지 확인하세요:

docker image pull public.ecr.aws/gravitational/teleport-distroless:16.2.0

프로젝트 디렉터리에서 다음 명령을 실행하세요:

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을 프록시함: http://172.17.0.4:15672
URL에 대한 새로운 애플리케이션 토큰 생성됨: http://172.17.0.3:15672
애플리케이션 서비스 컨테이너를 시작하여 URL을 프록시함: http://172.17.0.3:15672
URL에 대한 새로운 애플리케이션 토큰 생성됨: http://172.17.0.2:15672
애플리케이션 서비스 컨테이너를 시작하여 URL을 프록시함: http://172.17.0.2:15672

RabbitMQ 인스턴스가 Teleport에 등록되었는지 확인하세요:

tsh apps ls
Application Description Type Public Address Labels------------------- ----------- ---- ------------------------ -------------------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)

애플리케이션을 실행했던 터미널에서는 유사한 출력이 나타날 것입니다:

불필요한 애플리케이션 서비스 기록 삭제됨: rabbitmq-172-17-0-4
불필요한 애플리케이션 서비스 컨테이너 삭제됨: 63facaa3033a

tsh apps ls를 실행하면 이젠 두 개의 등록된 애플리케이션이 보여야 합니다.

다음 단계

우리는 Teleport API 클라이언트를 구현하여 등록된 애플리케이션을 실행 중인 Docker 컨테이너와 동기화 상태로 유지했습니다. Teleport API를 사용하여 귀하의 등록된 Teleport 리소스를 귀하의 서비스 검색 솔루션과 자동으로 동기화할 수 있습니다.

예제 상담

Teleport에는 자체 자동 리소스 검색 솔루션인 Teleport Discovery Service가 포함되어 있으며, Teleport의 검색 논리를 구현하는 방법을 확인하려면 소스를 참조할 수 있습니다.

Teleport 코드 리포지토리에는 프로덕션 준비 완료 Teleport API 클라이언트 예제가 포함되어 있습니다. 현재 자동으로 리소스를 검색하는 플러그인은 유지 관리되지 않지만, 이러한 예제를 사용하여 구성 파싱, 재시도 및 기타 작업을 구현하는 방법을 참조할 수 있습니다.

클라이언트 애플리케이션을 짧은 수명의 자격 증명으로 프로비전하기

이번 예제에서는 작성한 프로그램에 대한 자격 증명을 가져오기 위해 tctl auth sign 명령을 사용했습니다. 프로덕션 사용의 경우, 이러한 자격 증명이 도난당하는 위험을 줄이기 위해 기계 ID를 통해 짧은 수명의 자격 증명을 프로비전하는 것을 권장합니다. 자세한 사항은 기계 ID 문서를 참고하세요.

Teleport 원문 보기