Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

내부 개발자 플랫폼(IDP) 구축을 도와주는 Humanitec

Humanitec은 유명한 서비스는 아니다. 나도 Platform Engineering을 공부하다가 알게 된 회사고 platformengineering.org도 Humanitec이 운영하고 있다.

플랫폼 엔지니어링이란 용어를 가장 적극적으로 퍼트리고 있는 회사라고 할 수 있다. 플랫폼 엔지니어링이란 용어가 여전히 모호한 부분이 있지만 나에게 가장 중요한 부분은 해당 조직의 문제에 맞춰서 최적화하는 부분이라고 생각하기에 플랫폼 엔지니어링으로 사업을 하는 Humanitec이 계속 궁금했다. 어떻게 사업을 하는지도 궁금했지만, 플랫폼 엔지니어링을 아무래도 가장 많이 오랫동안 고민했을 거로 생각했기에 어떤 제품을 만들었을지 궁금해하던 차에 현재 하는 스터디 모임에서 발표를 해야할 일이 있어서 겸사겸사 Humanitec을 살펴봤다.

Humanitec

Humanitec 홈페이지

Humanitec 사이트를 보면 Humanitec으로 내부 개발자 플랫폼(IDP, Internal Developer Platform)을 적은 비용으로 빠르게 만들어서 다음을 이룰 수 있다고 설명하고 있다.

  • 표준화를 통해서 운영 업무를 줄이고
  • 셀프 서비스를 통해 리드 타임을 줄이고
  • 안전한 릴리스로 배포 주기를 줄일 수 있다.

Score + Platform Orchestrator + Drivers

Humanitec은 Score, Platform Orchestrator, Drivers 3가지로 구성되어 있다.

Score는 오픈소스로 공개되어 있는데 이 서비스가 어떤 구성으로 배포되어야 하는지를 정의하는 워크로드 명세서이다. 예를 들어 어떤 컨테이너 이미지를 사용하고 포트는 무엇이고 데이터베이스나 캐시는 무엇을 쓰는지를 정의해 놓고 배포할 때 어떻게 구성해야 하는지 알 수 있게 하는 것이다.

Platform Orchestrator는 문서에는 룰 엔진이라고 되어 있는데 그 정체가 아주 명확하지는 않지만 Humanitec 서비스 그 자체라고 이해해도 된다. 결국 이 인프라스트럭처를 표준화한다는 것은 인프라를 미리 만들어 두고 배포 시에 워크로드와 인프라를 연결하게 되는데 Score로 정의한 워크로드 명세를 가지고 어떤 인프라와 연결해야 할지 정책을 위반하진 않았는지를 검사한다.

Drivers는 인프라스트럭처를 프로비저닝하는 방법을 정의하는 방법을 제공한다. 데이터베이스나 캐시를 미리 만들어 둘 수도 있지만 어떻게 만들 수 있는지를 정의할 수 있다면 배포가 나가면서 해당 인프라스트럭처를 만들면서 워크로드와 연결해서 배포할 수 있는데 이를 Drivers로 할 수 있다.

Score + Platform Orchestrator + Drivers가 연결된 배포 파이프라인

세 가지 제품이 실제 개발 프로세스에서 어떻게 연결되는지를 좀 더 설명한 그림이다. Score 파일은 워크로드의 명세이므로 프로젝트의 저장소에 위치한다. CI에서 컨테이너에 이미지를 푸시하고 Platform Orchestrator에 요청하면 Platform Orchestrator는 Terraform 등으로 미리 만들어 놓은 리소스와 연결하거나 Drivers를 이용해서 그 순간 새로 리소스를 생성하고 워크로드와 연결해서 배포를 시작한다.

내가 업무로 만들고 있는 것도 비슷한 형태이기도 하고 이런 플랫폼을 구축하려면 필요한 것들이 많은데 각 컴포넌트를 제공하고 이를 이용해서 회사 내부에서 조직에 맞는 내부 개발자 플랫폼(IDP)을 빠르게 구축하라는 게 Humanitec의 컨셉이라고 이해했다.

Humanitec은 유용한가?

위에서 정리해서 설명했지만 여기까지 이해하기가 꽤 어려웠다. Humanitec의 글을 종종 보던 터라 기대하고 있었는데 만든 제품에 실망을 꽤 했다.

문서가 너무 부실한 상태이다. 여기서 말하는 Platform Engineering이 담아야 하는 영역이 꽤 크기 때문에 욕심이 컸던 것인지 문서가 너무 흩어져 있고 전체 컨셉을 이해하기 어렵다. 튜토리얼처럼 따라 해 보면 간단한 앱을 실제로 배포해 주면서 Humanitec이 제공하고자 하는 핵심을 빠르게 경험해 주는 게 제일 좋고 이를 위한 예제 구성도 잘 되어 있는 거 같은데 가능한 영역을 다 설명하고자 하는 욕심 때문인지 이렇게 따라가 볼 수 있는 문서 경로가 없어서 컨셉을 이용하는 데 상당한 시간이 걸렸다.

실제 프로덕트를 만들기 시작한지는 얼마 안되어서 인지 부실한 부분이 많다. Platform Engineering의 영역 때문인지도 모르지만 한번에 너무 큰 욕심을 보여서 전체가 부실한 느낌이 많이 들었다. 제품의 컨셉을 이해한 뒤에도 실제로 잘 동작할 거라는 의심이 꽤 들었고 AWS, GCP, Azure의 리소스를 다 커버하면서 Kubernetes 등까지 커버하려다 보니까 너무 규모가 큰데 아직 그 모든 규모를 커버할 역량은 안되어 보이기 때문에 실제로 완성도 있게 갈 수 있을지가 의심되는 상황이었다. 결국 드라이버만 봐도 Terrafrom처럼 모든 프로바이더에 대한 리소스 구현이 다 되어야 하는데 회사에 엄청난 노력이 있거나 오픈소스 생태계를 구성해야 하는데 현재로서는 둘 다 힘들어 보인다. 물론 Terraform 등 다른 도구로 리소스를 프로비저닝하고 이를 Platform Orchestrator에 등록해서 사용하는 것도 가능하지만 이조차도 오히려 복잡도를 너무 늘리지 않나 하는 생각이 들었다.

어느 정도 파악한 뒤의 느낌은 뭘 하고자 하는지는 이해했고 어느 정도 동의도 하지만 가능한가에 대한 의구심이 든 상태이다.

humanitec으로 앱 배포해 보기

실제로 가장 간단한 구성을 통해 Score, Platform Orchestrator, Drivers를 사용해서 앱을 배포해 보는 것이 Humanitec의 제품 의도를 이해해 보기 제일 좋다고 생각한다. 어떻게 사용하는지를 보면 대충 그 의도를 이해할 것이다.

먼저 Score CLI를 설치해야 한다. 이것도 이유는 모르겠지만 score-compose, score-helm, score-humanitec 3개의 CLI가 있어서 더욱 헷갈리는데 나는 가장 범용적으로 보이는 score-humanitec를 사용했다.

아래 내용으로 score.yaml 파일을 만든다.

apiVersion: score.dev/v1b1

metadata:
  name: hello-world

service:
  ports:
    www:
      port: 80

containers:
  hello:
    image: nginx

resources:
  dns:
    type: dns

컨테이너와 서비스 그리고 연결할 DNS 리소스가 정의된 것을 볼 수 있다. 가장 간단하게 구성하느라 코드도 사용하지 않고 nginx 컨테이너를 그대로 사용했다.

앱을 배포하기 전에 Humanitec에 가입해서 조직을 만들면 그 아래에서 앱을 만들 수 있다. 여기서는 demo라는 앱을 만들었다.

Humanitec의 어플리케이션 탭

demo 앱 상세로 들어가 보면 Development 환경이 기본으로 생성되어 있다. 실제 배포한다면 staging, production 등 추가 환경을 만들어서 쓰겠지만 여기서는 Development만 그대로 사용했고 아직 배포하지 않았으니 아무 기록도 없다.

Humanitec에 생성한 앱

아까 설치한 score-humanitec를 이용해서 배포하자. delta는 Score 파일에서 Humanitec의 배포 델타를 생성하는 명령어이고 --deploy 옵션을 사용하면 바로 배포를 진행한다. 여기선 로컬에서 바로 하지만 실제 사용한다면 CI/CD 파이프라인에 CLI를 설치해서 요청을 보낼 것이다.

$ score-humanitec delta \
  --env development \
  --app demo \
  --org="${HUMANITEC_ORG}" \
  --token "${HUMANITEC_TOKEN}" \
  --deploy

{
  "id": "b98217ea705bcfac33ab0e94c19a0538f006d64d",
  "metadata": {
    "env_id": "development",
    "name": "Auto-generated (SCORE)",
    "url": "https://app.humanitec.io/orgs/your_org/apps/demo/envs/development/draft/b98217ea705bcfac33ab0e94c19a0538f006d64d",
    "created_by": "s-c2b0c816-6275-4c13-a922-fe9134375dfb",
    "created_at": "2023-07-22T05:59:34.570576078Z",
    "last_modified_at": "2023-07-22T05:59:34.570576078Z"
  },
  "modules": {
    "add": {
      "hello-world": {
        "externals": {
          "dns": {
            "type": "dns"
          }
        },
        "profile": "humanitec/default-module",
        "spec": {
          "containers": {
            "hello": {
              "id": "hello",
              "image": "nginx"
            }
          },
          "service": {
            "ports": {
              "www": {
                "container_port": 80,
                "protocol": "TCP",
                "service_port": 80
              }
            }
          }
        }
      }
    }
  }
}

다시 Humanitec 사이트에 가보면 배포가 된 것을 볼 수 있다.

Humanitec에 표시된 배포 상태

배포된 앱의 상세에 가면 컨테이너의 로그 등을 볼 수 있고 연결된 DNS, 인그레스, 서비스 어카운트 등의 상태를 볼 수 있고 replicas 수의 조정 등 운영을 셀프서비스로 직접 할 수 있게 되어 있다.

배포된 버전의 상세 정보

score.yaml 파일에서 DNS 리소스를 연결했으므로 Humanitec에서 임시 도메인도 연결해 준다. DNS는 설정되었지만, 아직 실제 컨테이너로 연결은 되지 않는다.

여기서 따로 명시가 없었음에도 Kuberentes로 배포된 거를 볼 수 있다. 이는 테스트를 쉽게 해볼 수 있도록 Humanitec에서 미리 리소스를 만들어 두었기 때문이다. 리소스 탭을 보면 Kubernetes 클러스터뿐 아니라 DNS, 인그레스, 데이터베이스 등 테스트용 리소스가 정의된 걸 볼 수 있다.

Humanitec의 리소스 관리 탭

Default Humanitec Cluster에 들어가 보면 방금 배포한 demo 앱이 연결된 것을 확인할 수 있다.

Kubernetes 클러스터 리소스에 demo 앱이 연결됨

앞에서 배포할 때 로그를 보면 humanitec/default-module라는 프로필을 사용한 것을 알 수 있는데 이 프로필에 기본 클러스터 등이 연결되어 있을 것으로 추측한다. 이 프로필 관리는 어디서 하는지는 잘 모르겠다.

여기서 알 수 있는 것은 조직 내 인프라팀에서 용도에 따라 프로필을 만들고 그 아래 다양한 리소스를 생성해서 등록해 놓으면 서비스 개발팀에서 배포할 때 자동으로 연결되어서 배포되므로 인프라팀은 리소스를 표준화해서 관리하고 서비스 개발팀은 셀프서비스로 인프라 정책을 어기지 않고 빠르게 배포할 수 있게 한다는 것이다.

다시 돌아와서 컨테이너에 트래픽을 연결하려면 인그레스를 설정해야 한다.

score.yaml 파일 옆에 humanitec.score.yaml라는 파일을 만들고 다음 내용을 입력한다. 이는 Score에서는 Humanitec extension reference라고 부르는데 Humanitec에 특화된 부분을 확장하기 위한 명세라고 이해하면 된다. 아무래도 Score를 오픈소스로 만들어서 벤더 중립적으로 만들려다 보니 Humanitec에 특화된 부분을 별도로 분리한 것 같은데 이는 현시점에서는 좀 불필요한 복잡성이 아닌가 하는 생각이 들었다. 아래 설정에서는 인그레스를 추가했다.

apiVersion: humanitec.org/v1b1

profile: "humanitec/default-module"
spec:
  "ingress":
    rules:
      "${resources.dns}":
        http:
          "/":
            type: prefix
            port: 80

아까처럼 배포하지만 --extensions ./humanitec.score.yaml 옵션을 주어서 다시 한번 배포한다.

$ score-humanitec delta 
  --env development \
  --app demo \
  --org="${HUMANITEC_ORG}" \
  --token "${HUMANITEC_TOKEN}" \
  --extensions ./humanitec.score.yaml \
  --deploy

{
  "id": "99fc6280b5e3457221ac52055fc259232c49bcf2",
  "metadata": {
    "env_id": "development",
    "name": "Auto-generated (SCORE)",
    "url": "https://app.humanitec.io/orgs/your_org/apps/demo/envs/development/draft/99fc6280b5e3457221ac52055fc259232c49bcf2",
    "created_by": "s-c2b0c816-6275-4c13-a922-fe9134375dfb",
    "created_at": "2023-07-22T06:19:03.070519216Z",
    "last_modified_at": "2023-07-22T06:19:03.070519216Z"
  },
  "modules": {
    "add": {
      "hello-world": {
        "externals": {
          "dns": {
            "type": "dns"
          }
        },
        "profile": "humanitec/default-module",
        "spec": {
          "containers": {
            "hello": {
              "id": "hello",
              "image": "nginx"
            }
          },
          "ingress": {
            "rules": {
              "externals.dns": {
                "http": {
                  "/": {
                    "port": 80,
                    "type": "prefix"
                  }
                }
              }
            }
          },
          "service": {
            "ports": {
              "www": {
                "container_port": 80,
                "protocol": "TCP",
                "service_port": 80
              }
            }
          }
        }
      }
    }
  }
}

이제 배포에 다시 들어가 보면 인그레스가 설정된 것을 볼 수 있다.

배포된 앱의 인스레스 정보

해당 도메인에 연결하면 배포한 nginx 페이지가 잘 나오는 것을 확인할 수 있고 당연히 앞에서 본 리소스 탭의 인그레스에도 demo 앱에서 연결되어 있다고 표시된다.

연결된 nginx 페이지

배포하면서 리소스 연결하는 것을 살펴봤으니 이제 리소스 생성은 어떻게 하는지 Drivers를 살펴보자.

Drivers는 오픈소스라고 하는데 저장소는 못 찾았다. 문서를 보아도 아직 많은 리소스의 Drivers는 없고 AWS에는 Route53만 있어서 이를 사용해 보았다. 물론 커스텀 드라이버를 직접 만들 수도 있기는 하고 그냥 Terraform을 이용해서 리소스를 만든 뒤에 API로 Humanitec에 등록하는 것도 가능하다.

먼저 AWS를 사용해야 하니 Cloud Accounts에서 AWS의 API 키와 시크릿을 등록한다.

Humanitec의 클라우드 어카운트 탭

그리고 https://api.humanitec.io/orgs/your_org/resources/defs API를 이용해서 humanitec/dns-aws-route53 드라이버를 등록한다.

$ curl https://api.humanitec.io/orgs/your_org/resources/defs \
  -X POST \
  -H "Authorization: Bearer $HUMANITEC_TOKEN" \
  -H "Content-Type: application/json" \
  --data-binary '{
  "id": "route53",
  "name": "Route53",
  "type": "dns",
  "criteria": [
    {
      "app_id": "demo"
    }
  ],
  "account_id": "aws",
  "driver_type": "humanitec/dns-aws-route53",
  "driver_inputs": {
    "values": {
      "domain": "example.outsider.domain",
      "hosted_zone_id": "Z01234567890J"
    }
  }
}'

{
  "org_id": "your_org",
  "id": "route53",
  "name": "Route53",
  "type": "dns",
  "driver_type": "humanitec/dns-aws-route53",
  "driver_inputs": {
    "values": {
      "domain": "example.outsider.domain",
      "hosted_zone_id": "Z01234567890J"
    }
  },
  "created_by": "s-c2b0c816-6275-4c13-a922-fe9134375dfb",
  "created_at": "2023-07-22T06:44:03.69865496Z",
  "criteria": [{
    "app_id": "demo",
    "id": "8b5b5a186b2029e4"
  }]
}

Humanitec의 Resource Management에 가보면 Route53이라는 리소스가 등록된 것을 볼 수 있다.

리소스 관리에 등록된 AWS Route53 리소스

이것만으로는 Route53을 생성하지 않는다. 아까 배포했던 score.yaml 파일을 다음과 같이 수정한다.

apiVersion: score.dev/v1b1

metadata:
  name: hello-world

service:
  ports:
    www:
      port: 80

containers:
  hello:
    image: nginx

resources:
  route53:
    type: dns

달라진 부분은 리소스의 dns 부분인데 아까는 아래처럼 되어 있어서 dns 타입의 이름이 dns였지만 이번에는 새로 생성한 리소스인 route53으로 변경했다.

resources:
  dns:
    type: dns

다시 배포를 진행하면 AWS Route53에 A 레코드가 바로 생성되는 것을 볼 수 있다.

AWS Route53에 등록된 A 레코드

이렇게 Drivers를 이용하면 어떻게 리소스를 생성하는지를 정의해 두고 서비스 개발팀에서는 Score 파일에서 리소스를 정의하면 배포 시점에 생성되면서 연결해서 바로 사용할 수 있게 된다.

2023/07/22 16:56 2023/07/22 16:56