Skip to content

필사 모드: DevOps 내부 개발자 플랫폼 플레이북 2026

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

Play 1: IDP가 필요한 시점 판단하기

Internal Developer Platform(IDP)은 개발자가 인프라 요청 없이 셀프서비스로 서비스를 생성, 배포, 모니터링할 수 있는 내부 플랫폼이다. 2026년 Gartner 조사에 따르면 엔터프라이즈의 80%가 플랫폼 엔지니어링에 투자하고 있다.

하지만 모든 조직에 IDP가 필요한 것은 아니다. 다음 조건 중 3개 이상 해당되면 IDP 도입을 검토할 시점이다.

**도입 신호 체크리스트:**

- [ ] 서비스 수가 20개를 초과했다

- [ ] 신규 서비스 생성에 1주일 이상 걸린다

- [ ] 팀마다 CI/CD 파이프라인이 다르고 표준이 없다

- [ ] 온보딩에 2주 이상 소요된다 (개발 환경 설정, 권한 요청 등)

- [ ] 인프라팀이 반복적인 Jira 티켓 처리에 80% 이상의 시간을 쓴다

- [ ] 배포 후 장애 시 롤백 절차가 팀마다 다르거나 문서화되어 있지 않다

- [ ] 비용 귀속(cost attribution)이 불가능하다

3개 미만이라면 IDP보다 간단한 셸 스크립트 자동화나 GitHub Actions 표준 템플릿만으로도 충분하다.

Play 2: 플랫폼 팀 구성과 역할 정의

IDP는 제품이다. 인프라 엔지니어 몇 명이 사이드 프로젝트로 만드는 것이 아니라, 전담 팀이 제품 마인드셋으로 운영해야 한다.

팀 구성 (50-200명 개발 조직 기준)

| 역할 | 인원 | 핵심 책임 |

| ------------------ | ------------ | ----------------------------------------------- |

| Platform PM | 1명 | 개발자 요구사항 수집, 로드맵 관리, 채택률 추적 |

| Platform Engineer | 2-3명 | 인프라 추상화, API/UI 개발, 골든패스 설계 |

| SRE / DevOps | 1-2명 | 모니터링 파이프라인, 온콜, 인시던트 대응 자동화 |

| Developer Advocate | 0.5명 (겸직) | 문서화, 온보딩 가이드, 내부 교육 |

핵심 원칙:

- **플랫폼 팀의 고객은 내부 개발자다.** NPS(Net Promoter Score)를 분기마다 측정한다.

- **채택은 강제가 아니라 매력으로.** 골든패스를 따르면 30분에 끝나지만, 따르지 않으면 2주가 걸리게 만든다.

- **피드백 루프는 2주 이내로.** 개발자가 요청한 기능이 2주 내에 최소 응답(구현 계획 또는 거절 사유)을 받아야 한다.

Play 3: 기술 스택 선택

Backstage vs 자체 구축 vs SaaS 비교

| 기준 | Backstage (오픈소스) | 자체 구축 | SaaS (Port/Cortex 등) |

| ---------------- | --------------------------- | --------------- | --------------------- |

| 초기 비용 | 중간 (3-6개월 구축) | 높음 (6-12개월) | 낮음 (즉시 시작) |

| 커스터마이징 | 높음 (플러그인 생태계) | 최고 | 제한적 |

| 유지보수 부담 | 높음 (업그레이드, 보안패치) | 매우 높음 | 없음 (벤더 책임) |

| 조직 규모 적합성 | 100명+ | 500명+ | 50-300명 |

| 벤더 종속 | 없음 | 없음 | 높음 |

| 플러그인/통합 | 2000+ 플러그인 | 필요한 것만 | 벤더 제공 범위 |

**권장 전략**: 100명 이하 조직이면 SaaS부터 시작한다. 100-500명이면 Backstage를 도입하되, Roadie 같은 관리형 Backstage도 고려한다. 500명 이상이면 자체 팀이 Backstage를 커스터마이징하여 운영한다.

Play 4: Backstage 셋업과 Software Catalog

Backstage 설치

Backstage CLI로 신규 프로젝트 생성

npx @backstage/create-app@latest --skip-install

결과 디렉토리 구조

my-backstage/

├── app-config.yaml # 핵심 설정 파일

├── app-config.production.yaml

├── packages/

│ ├── app/ # 프론트엔드 (React)

│ └── backend/ # 백엔드 (Node.js)

├── plugins/ # 커스텀 플러그인

├── catalog-info.yaml # 이 프로젝트 자체의 카탈로그 등록

└── package.json

의존성 설치 및 실행

cd my-backstage

yarn install

yarn dev

http://localhost:3000 에서 접속

app-config.yaml 핵심 설정

app-config.yaml

app:

title: 'MyOrg Developer Platform'

baseUrl: http://localhost:3000

organization:

name: 'MyOrg'

backend:

baseUrl: http://localhost:7007

database:

client: pg

connection:

host: ${POSTGRES_HOST}

port: ${POSTGRES_PORT}

user: ${POSTGRES_USER}

password: ${POSTGRES_PASSWORD}

GitHub 통합 (서비스 카탈로그 자동 탐색)

integrations:

github:

- host: github.com

token: ${GITHUB_TOKEN}

Software Catalog 설정

catalog:

import:

entityFilename: catalog-info.yaml

rules:

- allow: [Component, System, API, Resource, Location, Group, User]

locations:

조직의 모든 저장소에서 catalog-info.yaml 자동 탐색

- type: github-discovery

target: https://github.com/my-org/*/blob/main/catalog-info.yaml

수동 등록

- type: file

target: ./catalog-entities/all-systems.yaml

인증 (GitHub OAuth)

auth:

environment: development

providers:

github:

development:

clientId: ${GITHUB_OAUTH_CLIENT_ID}

clientSecret: ${GITHUB_OAUTH_CLIENT_SECRET}

서비스 카탈로그 등록 표준

각 서비스 저장소의 루트에 `catalog-info.yaml`을 배치한다.

catalog-info.yaml

apiVersion: backstage.io/v1alpha1

kind: Component

metadata:

name: order-service

description: '주문 처리 마이크로서비스'

annotations:

github.com/project-slug: my-org/order-service

backstage.io/techdocs-ref: dir:.

datadoghq.com/dashboard-url: https://app.datadoghq.com/dashboard/abc-123

pagerduty.com/service-id: PXXXXXX

argocd/app-name: order-service-prod

tags:

- java

- spring-boot

- tier-1

links:

- url: https://order.internal.example.com

title: 프로덕션 URL

- url: https://grafana.internal.example.com/d/order-service

title: Grafana 대시보드

spec:

type: service

lifecycle: production

owner: team-commerce

system: commerce-platform

dependsOn:

- component:payment-service

- resource:orders-database

providesApis:

- order-api

consumesApis:

- payment-api

- inventory-api

apiVersion: backstage.io/v1alpha1

kind: API

metadata:

name: order-api

description: '주문 REST API'

spec:

type: openapi

lifecycle: production

owner: team-commerce

definition:

$text: ./docs/openapi.yaml

Play 5: 서비스 템플릿 (Scaffolding)

Backstage의 Software Templates는 신규 서비스를 표준화된 구조로 생성하는 기능이다. 30분 내에 CI/CD, 모니터링, 카탈로그 등록까지 완료된 서비스를 만들 수 있다.

templates/spring-boot-service/template.yaml

apiVersion: scaffolder.backstage.io/v1beta3

kind: Template

metadata:

name: spring-boot-service

title: 'Spring Boot 마이크로서비스'

description: 'CI/CD, 모니터링, 카탈로그가 자동 설정된 Spring Boot 서비스를 생성합니다'

tags:

- java

- spring-boot

- recommended

spec:

owner: team-platform

type: service

parameters:

- title: 서비스 정보

required:

- serviceName

- ownerTeam

- tier

properties:

serviceName:

title: 서비스 이름

type: string

pattern: '^[a-z][a-z0-9-]*$'

description: '소문자, 숫자, 하이픈만 허용'

ownerTeam:

title: 소유 팀

type: string

ui:field: OwnerPicker

ui:options:

catalogFilter:

kind: Group

tier:

title: 서비스 티어

type: string

enum: ['tier-1', 'tier-2', 'tier-3']

enumNames: ['Tier 1 (99.99% SLO)', 'Tier 2 (99.9% SLO)', 'Tier 3 (99% SLO)']

javaVersion:

title: Java 버전

type: string

default: '21'

enum: ['17', '21']

- title: 인프라 설정

properties:

database:

title: 데이터베이스

type: string

default: 'postgresql'

enum: ['postgresql', 'mysql', 'none']

messageQueue:

title: 메시지 큐

type: string

default: 'none'

enum: ['kafka', 'rabbitmq', 'none']

replicaCount:

title: 기본 레플리카 수

type: integer

default: 3

minimum: 1

maximum: 20

steps:

1. 템플릿에서 저장소 생성

- id: fetch-template

name: 템플릿 코드 생성

action: fetch:template

input:

url: ./skeleton

values:

serviceName: ${{ parameters.serviceName }}

ownerTeam: ${{ parameters.ownerTeam }}

tier: ${{ parameters.tier }}

javaVersion: ${{ parameters.javaVersion }}

database: ${{ parameters.database }}

replicaCount: ${{ parameters.replicaCount }}

2. GitHub 저장소 생성

- id: publish

name: GitHub 저장소 생성

action: publish:github

input:

allowedHosts: ['github.com']

repoUrl: github.com?owner=my-org&repo=${{ parameters.serviceName }}

defaultBranch: main

repoVisibility: internal

protectDefaultBranch: true

requireCodeOwnerReviews: true

3. ArgoCD 앱 등록

- id: register-argocd

name: ArgoCD 애플리케이션 등록

action: argocd:create-resources

input:

appName: ${{ parameters.serviceName }}-prod

argoInstance: main

namespace: ${{ parameters.serviceName }}

repoUrl: https://github.com/my-org/${{ parameters.serviceName }}

path: k8s/overlays/production

4. Backstage 카탈로그 등록

- id: register-catalog

name: 카탈로그 등록

action: catalog:register

input:

repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }}

catalogInfoPath: /catalog-info.yaml

output:

links:

- title: 저장소

url: ${{ steps['publish'].output.remoteUrl }}

- title: 카탈로그

icon: catalog

entityRef: ${{ steps['register-catalog'].output.entityRef }}

Play 6: TechDocs로 문서 통합

분산된 문서를 Backstage TechDocs로 통합하면, 서비스 카탈로그에서 바로 기술 문서를 확인할 수 있다.

mkdocs.yml (각 서비스 저장소 루트)

site_name: order-service

nav:

- Home: index.md

- Architecture: architecture.md

- API Reference: api.md

- Runbook: runbook.md

- ADR:

- adr/001-database-choice.md

- adr/002-event-schema.md

plugins:

- techdocs-core

<!-- docs/runbook.md -->

Order Service 운영 런북

장애 대응

주문 처리 지연 (P95 > 500ms)

1. Grafana 대시보드 확인: [링크]

2. DB 커넥션 풀 상태 확인:

bash

kubectl exec -it deploy/order-service -- curl localhost:8080/actuator/metrics/hikaricp.connections.active

3. 커넥션 풀 포화 시:

kubectl scale deploy/order-service --replicas=6

4. DB 슬로우 쿼리 확인:

SELECT pid, now() - query_start AS duration, query

FROM pg_stat_activity

WHERE state = 'active' AND now() - query_start > interval '5s';

주문 생성 실패 (HTTP 500)

1. 에러 로그 확인:

kubectl logs deploy/order-service --tail=100 | grep ERROR

2. 에러 코드별 대응:

- ORDER-001: 결제 서비스 연결 실패 -> payment-service 상태 확인

- ORDER-002: 재고 부족 -> inventory-service 동기화 확인

- ORDER-003: DB 데드락 -> 트랜잭션 격리 수준 확인

Play 7: 셀프서비스 인프라 프로비저닝

개발자가 Backstage UI에서 데이터베이스, 메시지 큐, 캐시 등을 직접 프로비저닝할 수 있게 한다. 실제 인프라 생성은 Terraform + GitOps로 처리한다.

templates/provision-postgresql/template.yaml

apiVersion: scaffolder.backstage.io/v1beta3

kind: Template

metadata:

name: provision-postgresql

title: 'PostgreSQL 데이터베이스 프로비저닝'

description: 'RDS PostgreSQL 인스턴스를 셀프서비스로 생성합니다'

spec:

owner: team-platform

type: resource

parameters:

- title: 데이터베이스 설정

required:

- dbName

- environment

- instanceClass

properties:

dbName:

title: DB 이름

type: string

pattern: '^[a-z][a-z0-9_]*$'

environment:

title: 환경

type: string

enum: ['dev', 'staging', 'production']

instanceClass:

title: 인스턴스 크기

type: string

default: 'db.r7g.large'

enum:

- 'db.t4g.medium'

- 'db.r7g.large'

- 'db.r7g.xlarge'

- 'db.r7g.2xlarge'

enumNames:

- 'Small (2 vCPU, 4GB) - dev/staging'

- 'Medium (2 vCPU, 16GB) - production'

- 'Large (4 vCPU, 32GB) - production'

- 'XLarge (8 vCPU, 64GB) - high traffic'

storageGb:

title: 스토리지 (GB)

type: integer

default: 100

minimum: 20

maximum: 16000

multiAz:

title: Multi-AZ 배포

type: boolean

default: false

steps:

- id: create-terraform-pr

name: Terraform PR 생성

action: publish:github:pull-request

input:

repoUrl: github.com?owner=my-org&repo=infrastructure

branchName: provision-db-${{ parameters.dbName }}

title: 'DB 프로비저닝: ${{ parameters.dbName }} (${{ parameters.environment }})'

description: |

자동 생성된 DB 프로비저닝 요청입니다.

- DB 이름: ${{ parameters.dbName }}

- 환경: ${{ parameters.environment }}

- 인스턴스: ${{ parameters.instanceClass }}

- 스토리지: ${{ parameters.storageGb }}GB

- Multi-AZ: ${{ parameters.multiAz }}

targetPath: terraform/rds/${{ parameters.environment }}/${{ parameters.dbName }}

sourcePath: ./terraform-template

Play 8: 플랫폼 성공 지표 측정

IDP의 성과를 측정하지 않으면 투자 대비 효과를 증명할 수 없다. 다음 지표를 분기별로 추적한다.

핵심 성과 지표 (KPI)

platform_metrics.py - 플랫폼 KPI 대시보드 데이터 수집

from datetime import datetime, timedelta

class PlatformMetrics:

def __init__(self, github_token: str, backstage_url: str):

self.github = github_token

self.backstage = backstage_url

def service_creation_lead_time(self) -> dict:

"""신규 서비스 생성 소요 시간 (목표: 30분 이내)"""

Backstage scaffolder 로그에서 추출

response = requests.get(

f"{self.backstage}/api/scaffolder/v2/tasks",

params={"createdAfter": (datetime.now() - timedelta(days=90)).isoformat()}

)

tasks = response.json()["items"]

lead_times = []

for task in tasks:

if task["status"] == "completed":

start = datetime.fromisoformat(task["createdAt"])

end = datetime.fromisoformat(task["completedAt"])

lead_times.append((end - start).total_seconds() / 60)

return {

"median_minutes": sorted(lead_times)[len(lead_times) // 2],

"p95_minutes": sorted(lead_times)[int(len(lead_times) * 0.95)],

"total_services_created": len(lead_times),

}

def golden_path_adoption_rate(self) -> dict:

"""골든패스 채택률 (목표: 80% 이상)"""

GitHub API에서 reusable workflow 사용 현황 조회

repos = requests.get(

"https://api.github.com/orgs/my-org/repos",

headers={"Authorization": f"token {self.github}"},

params={"per_page": 100, "type": "internal"}

).json()

using_golden_path = 0

total_active = 0

for repo in repos:

if repo["archived"]:

continue

total_active += 1

CI 워크플로우에서 골든패스 참조 확인

workflows = requests.get(

f"https://api.github.com/repos/my-org/{repo['name']}/actions/workflows",

headers={"Authorization": f"token {self.github}"}

).json()

for wf in workflows.get("workflows", []):

if "golden" in wf.get("path", "").lower():

using_golden_path += 1

break

return {

"adoption_rate": using_golden_path / max(total_active, 1),

"using_golden_path": using_golden_path,

"total_active_repos": total_active,

}

def developer_nps(self) -> dict:

"""개발자 만족도 NPS (목표: 30 이상)"""

분기별 서베이 결과 (Google Forms / Typeform 등)

직접 API 연동하거나, 수동 입력

return {

"nps_score": 42,

"promoters_pct": 55,

"detractors_pct": 13,

"response_rate": 0.72,

"top_complaints": [

"빌드 시간이 느림",

"로그 검색 UI가 불편함",

"권한 요청 자동화 부족",

]

}

KPI 목표값

| 지표 | 나쁨 | 보통 | 좋음 | 목표 |

| ----------------- | -------- | ------ | ------ | ------ |

| 서비스 생성 시간 | 1주+ | 1-3일 | 1시간 | 30분 |

| 골든패스 채택률 | 30% 미만 | 30-60% | 60-80% | 80%+ |

| 개발자 NPS | 0 미만 | 0-20 | 20-40 | 40+ |

| 온보딩 시간 | 2주+ | 1-2주 | 2-5일 | 1일 |

| 인프라 티켓 수/월 | 50+ | 20-50 | 5-20 | 5 미만 |

Play 9: 트러블슈팅

문제 1: Backstage 카탈로그 동기화 지연

WARN: Entity refresh for component:order-service took 45s (threshold: 10s)

원인: GitHub discovery가 수백 개 저장소를 스캔하면서 API rate limit에 걸린다.

해결: 스캔 범위 제한 + 캐시 설정

catalog:

providers:

github:

myOrg:

organization: 'my-org'

catalogPath: '/catalog-info.yaml'

filters:

repository: '^(?!archived-).*$' # archived- 접두사 저장소 제외

topic:

include: ['backstage-enabled'] # 토픽 기반 필터링

schedule:

frequency: { minutes: 30 } # 30분 주기 (기본 5분)

timeout: { minutes: 5 }

문제 2: Software Template 실행 실패 - GitHub 권한

Error: Resource not accessible by integration

HttpError: 403 - Resource not accessible by integration

원인: GitHub App의 권한이 부족하거나, 생성하려는 저장소의 organization에 대한 접근 권한이 없다.

GitHub App 권한 확인

Settings > Developer settings > GitHub Apps > [앱 이름] > Permissions

필요 권한:

- Repository: Administration (Read & Write)

- Repository: Contents (Read & Write)

- Organization: Members (Read)

또는 Personal Access Token(PAT) 사용 시 필요 scope:

repo, workflow, admin:org

문제 3: TechDocs 빌드 실패

mkdocs build failed: No module named 'techdocs_core'

해결: TechDocs 빌드 환경에 플러그인 설치

pip install mkdocs-techdocs-core

Docker 빌드 사용 시

docker run --rm -v $(pwd):/content \

spotify/techdocs:latest \

build --site-dir /content/site

app-config.yaml에서 빌드 방식 설정

techdocs:

builder: 'external' # CI에서 빌드

publisher:

type: 'awsS3'

awsS3:

bucketName: 'my-org-techdocs'

region: 'ap-northeast-2'

문제 4: 플랫폼 채택률이 올라가지 않음

이것은 기술 문제가 아니라 조직 문제다.

**해결 전략:**

1. **챔피언 팀을 먼저 확보한다**: 얼리어답터 2-3개 팀을 선정하고, 이 팀의 성공 사례를 내부에 공유한다.

2. **마찰을 제거한다**: 개발자가 골든패스를 따르지 않을 때 겪는 고통(수동 배포, 수동 모니터링 설정)을 유지하면서, 골든패스의 편의성을 극대화한다.

3. **강제하지 않는다**: 매달 "Platform Day"를 열어 데모와 피드백 세션을 진행한다.

4. **지표로 증명한다**: "골든패스를 사용하는 팀의 배포 빈도가 3배 높다"와 같은 데이터를 공유한다.

Play 10: IDP 로드맵 단계별 구현

모든 것을 한번에 만들려고 하면 실패한다. 3단계로 나누어 점진적으로 구축한다.

Phase 1 (1-3개월): 기초

- Software Catalog 구축 (모든 서비스, 팀, API 등록)

- CI 골든패스 표준화 (GitHub Actions reusable workflow)

- 서비스 생성 템플릿 1-2개

Phase 2 (4-6개월): 확장

- CD 골든패스 (Argo Rollouts 카나리 배포)

- TechDocs 통합 (런북, ADR)

- 셀프서비스 인프라 프로비저닝 (DB, 캐시)

- 비용 태깅 및 대시보드

Phase 3 (7-12개월): 성숙

- 보안 정책 자동 적용 (OPA/Kyverno)

- DORA 지표 자동 수집 및 대시보드

- 내부 마켓플레이스 (공유 라이브러리, 플러그인)

- 개발 환경 원클릭 프로비저닝

퀴즈

정답: ||서비스 수가 20개 미만이고, 인프라팀의 반복 티켓 처리 비율이 낮으며, 신규 서비스 생성이

1주일 이내에 가능한 조직이다. 이 경우 IDP보다 간단한 스크립트 자동화나 표준 CI/CD 템플릿으로

충분하다.||

정답: ||IDP는 내부 제품이므로 고객(개발자)의 요구사항 수집, 우선순위 결정, 채택률 측정이 필요하다.

엔지니어만으로 구성하면 기술 중심으로 치우쳐 개발자가 실제로 필요한 기능이 아닌 기술적으로

흥미로운 기능을 만들게 된다.||

Q3. Backstage Software Catalog에서 catalog-info.yaml의 dependsOn 필드가 중요한 이유는?

정답: ||서비스 간 의존성을 명시하여 장애 영향 범위를 즉시 파악할 수 있다. order-service가

payment-service에 dependsOn이면, payment-service 장애 시 order-service도 영향받는다는 것을

카탈로그에서 바로 확인할 수 있다.||

정답: ||서비스 생성과 동시에 GitOps 기반 배포 파이프라인이 자동 구성되어, 개발자가 코드를 push하면

즉시 배포가 시작된다. 이 단계가 없으면 개발자가 별도로 ArgoCD 설정을 요청하는 티켓을 생성해야

하며, 이것이 온보딩 시간을 늘리는 주요 원인이다.||

정답: ||골든패스를 따르지 않을 때의 불편함을 유지하면서(수동 배포 2주, 수동 모니터링 설정),

골든패스의 편의성(30분 내 서비스 생성, 자동 배포, 자동 모니터링)을 극대화한다. 챔피언 팀의 성공

사례를 공유하고, 지표로 효과를 증명한다.||

정답: ||모든 기능을 한번에 구축하면 12개월 이상 소요되어 ROI를 증명하기 전에 프로젝트가 취소될 수

있다. Phase 1(3개월)에서 카탈로그와 CI 표준화로 빠르게 가치를 보여주고, 이 성과를 기반으로 Phase

2, 3의 투자를 확보한다.||

정답: ||응답률이 70% 이상이어야 의미있는 지표다. 또한 NPS 점수만 보지 말고 detractor(비추천자)의

구체적 불만 사항을 분석해야 한다. top_complaints를 분기별로 추적하여 개선 여부를 확인하고, 개선된

항목을 공개적으로 알려 피드백 루프를 닫아야 한다.||

References

- [Backstage by Spotify](https://backstage.io/)

- [Gartner: What Is Platform Engineering](https://www.gartner.com/en/articles/what-is-platform-engineering)

- [CNCF Platforms White Paper](https://tag-app-delivery.cncf.io/whitepapers/platforms/)

- [Backstage Software Templates](https://backstage.io/docs/features/software-templates/)

- [Internal Developer Platform Guide - Humanitec](https://internaldeveloperplatform.org/)

현재 단락 (1/463)

Internal Developer Platform(IDP)은 개발자가 인프라 요청 없이 셀프서비스로 서비스를 생성, 배포, 모니터링할 수 있는 내부 플랫폼이다. 2026년 Gart...

작성 글자: 0원문 글자: 13,425작성 단락: 0/463