개요 및 실전 배포 목표
SAP BTP Cockpit은 시각적으로 편리하지만, 반복 배포·CI/CD·원격 서버 작업에서는 오히려 병목이 됩니다. 이 글은 Cloud Foundry CLI(cf CLI) 하나만으로 로그인부터 서비스 바인딩, 매니페스트 배포, 롤백까지 전체 흐름을 완성하는 방법을 실무 관점에서 정리합니다. 특히 야간 배포·다중 스페이스 관리·파이프라인 자동화에 바로 적용할 수 있도록 SalesOrder 서비스 예제를 중심으로 진행합니다.
- 체크리스트 1: cf CLI v8 설치 및 SAP BTP CF API 엔드포인트 확인
- 체크리스트 2: Org / Space 타깃팅과 인증 토큰 관리
- 체크리스트 3: manifest.yml 작성 및 서비스 인스턴스 바인딩
- 체크리스트 4: Blue-Green 무중단 배포와 롤백 절차 습득
사전 준비 지식
이 글은 Cloud Foundry의 기본 개념(Org, Space, App, Buildpack)을 알고 있고, Node.js 또는 Java 애플리케이션을 로컬에서 실행해 본 개발자를 대상으로 합니다. YAML 문법과 기본 셸 명령(curl, jq)에 익숙하면 이해가 훨씬 빨라집니다. BTP Trial 또는 Enterprise 계정, 그리고 최소 1개의 CF Space 사용 권한이 필요합니다.
환경 및 버전 준비물
실습 환경은 다음 기준으로 검증했습니다. 버전이 다르면 명령어 옵션 이름이 미묘하게 달라질 수 있으니 주의합니다.
- SAP BTP: Cloud Foundry Runtime (Multi-Cloud, AWS ap-northeast-2 기준)
- cf CLI: v8.7.x (v6은 legacy이므로 v8 권장)
- Node.js: 20 LTS 또는 Java 17 (SAP Java Buildpack 사용 시)
- 운영체제: macOS 14 / Ubuntu 22.04 / Windows 11 WSL2
- 보조 도구: jq 1.6+, git 2.40+
macOS는 brew install cloudfoundry/tap/cf-cli@8, Windows는 Chocolatey로 choco install cloudfoundry-cli로 설치합니다. 리눅스는 Cloud Foundry 공식 apt 리포지토리를 등록한 뒤 apt-get install cf8-cli를 실행합니다. 설치 후 cf --version으로 cf version 8.x.x가 출력되는지 확인합니다.
핵심 개념: cf push가 내부에서 하는 일
Cloud Foundry의 배포는 단순히 파일을 서버에 올리는 것이 아닙니다. cf push는 마치 "택배 자동 분류 컨베이어"처럼 여러 단계를 순차적으로 실행합니다.
- Upload: 로컬 소스를 Blob Store에 압축 업로드. 이전에 올린 파일과 fingerprint(SHA)를 비교해 변경분만 전송합니다.
- Stage: Diego Cell이 Buildpack을 실행해 소스 코드를 droplet(실행 가능한 바이너리 스냅샷)으로 변환합니다.
- Run: droplet을 지정된 인스턴스 수만큼 컨테이너(Garden-runC)에서 실행하고 라우터에 URL을 등록합니다.
Cockpit UI는 이 세 단계를 진행률 바로 보여줄 뿐, 실제 실행은 모두 CLI가 호출하는 Cloud Controller REST API로 이뤄집니다. 즉, CLI만으로 100% 동일한 작업이 가능하며 오히려 세부 옵션 제어에서 우위를 가집니다.
비유: Cockpit이 "완성된 조리 메뉴판"이라면, cf CLI는 "재료·불세기·시간을 직접 조정할 수 있는 주방"입니다. 자동화·재현성이 중요할수록 CLI 쪽이 유리합니다.
또한 조직 계층은 Global Account → Subaccount → Org → Space → App 순서입니다. CLI에서 로그인 후 cf target으로 특정 Org/Space에 "포커스"를 맞추면, 이후 명령은 그 Space 안의 리소스에만 적용됩니다. 여러 스테이지(dev/qa/prd)를 오갈 때는 target 스위칭이 핵심입니다.
실전 예제 1단계: 최소 구성으로 SalesOrder 앱 배포
먼저 아주 단순한 Node.js Express 앱을 배포해 배포 사이클을 체감합니다. 프로젝트 구조는 다음과 같이 만듭니다.
{
"name": "salesorder-api",
"version": "0.1.0",
"main": "server.js",
"engines": { "node": "20.x" },
"scripts": { "start": "node server.js" },
"dependencies": { "express": "^4.19.2" }
}
// server.js - SalesOrder 최소 API
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
app.get('/orders/:id', (req, res) => {
res.json({
orderId: req.params.id,
customer: 'ACME Corp',
amount: 12500,
currency: 'EUR',
status: 'CONFIRMED'
});
});
app.listen(port, () => console.log(`SalesOrder API listening on ${port}`));
CF API에 로그인하고 타깃을 지정한 뒤 바로 push 합니다. 리전별 API 엔드포인트는 api.cf.{region}.hana.ondemand.com 형식입니다.
# cf login (대화형)
cf api https://api.cf.ap21.hana.ondemand.com
cf login -u your.email@company.com
# Org / Space 확인 및 타깃
cf orgs
cf target -o "your-trial-org" -s "dev"
# 배포 (manifest 없이 CLI 인자만으로도 가능)
cf push salesorder-api -m 128M -k 512M --random-route
배포가 성공하면 routes 항목에 salesorder-api-*.cfapps.ap21.hana.ondemand.com 형태 URL이 표시됩니다. curl으로 /orders/1001을 호출해 응답을 확인합니다.
실전 예제 2단계: manifest.yml, 서비스 바인딩, 로그 관찰
실제 프로젝트는 인자 나열보다 manifest.yml로 선언적 관리가 필요합니다. XSUAA(인증)와 Destination 서비스를 바인딩하고, 환경변수를 분리합니다.
# manifest.yml
---
applications:
- name: salesorder-api
memory: 256M
disk_quota: 1G
instances: 2
buildpacks:
- nodejs_buildpack
stack: cflinuxfs4
routes:
- route: salesorder-api-dev.cfapps.ap21.hana.ondemand.com
env:
NODE_ENV: production
LOG_LEVEL: info
services:
- salesorder-xsuaa
- salesorder-dest
health-check-type: http
health-check-http-endpoint: /health
서비스 인스턴스는 CLI로 사전에 생성합니다. 서비스 카탈로그를 조회하고, 원하는 플랜으로 생성한 뒤 앱에 자동 바인딩되게 합니다.
cf marketplace
cf marketplace -e xsuaa
# xs-security.json은 스코프·역할 정의 파일
cf create-service xsuaa application salesorder-xsuaa -c xs-security.json
cf create-service destination lite salesorder-dest
# 서비스 준비 대기 (비동기 프로비저닝)
cf service salesorder-xsuaa
# manifest로 배포
cf push -f manifest.yml
애플리케이션 안에서는 서비스 접속 정보를 VCAP_SERVICES 환경변수에서 읽습니다. 이를 안전하게 처리하도록 로깅과 예외 방어 코드를 추가합니다.
// config.js - VCAP_SERVICES 파싱 + 방어 로직
function loadXsuaa() {
try {
const vcap = JSON.parse(process.env.VCAP_SERVICES || '{}');
const xsuaa = (vcap.xsuaa || [])[0];
if (!xsuaa) throw new Error('xsuaa binding not found');
return xsuaa.credentials;
} catch (err) {
console.error('[CONFIG] failed to parse VCAP_SERVICES', err.message);
process.exit(1);
}
}
module.exports = { xsuaa: loadXsuaa() };
운영 중 로그와 이벤트는 다음 명령으로 관찰합니다. 대부분의 배포 실패는 cf logs의 [STG/0](staging)와 [APP/PROC/WEB/0](런타임) 라인에서 원인이 드러납니다.
cf logs salesorder-api --recent
cf logs salesorder-api # 실시간 tail
cf events salesorder-api
cf app salesorder-api
실전 예제 3단계: Blue-Green 무중단 배포와 CI/CD 스크립트
프로덕션에서는 cf push가 기본으로 트리거하는 재시작만으로도 수십 초 다운타임이 생길 수 있습니다. cf CLI v7+는 --strategy rolling을 제공하고, 더 안전한 방식은 별도 라우트를 사용하는 Blue-Green입니다.
# 방법 A: Rolling deployment (간단, in-place)
cf push salesorder-api -f manifest.yml --strategy rolling
# 방법 B: Blue-Green (완전 격리, 롤백 즉시)
APP=salesorder-api
NEW=${APP}-green
ROUTE=salesorder-api.cfapps.ap21.hana.ondemand.com
cf push $NEW -f manifest.yml -n ${APP}-green --no-route
cf map-route $NEW cfapps.ap21.hana.ondemand.com -n ${APP}-green
# 헬스체크 통과 확인 후 트래픽 스위칭
curl -fsS https://${APP}-green.cfapps.ap21.hana.ondemand.com/health
cf map-route $NEW cfapps.ap21.hana.ondemand.com -n salesorder-api
cf unmap-route $APP cfapps.ap21.hana.ondemand.com -n salesorder-api
cf rename $APP ${APP}-old
cf rename $NEW $APP
cf delete ${APP}-old -f
CI/CD에서는 대화형 로그인 대신 service key 또는 SSO passcode를 이용한 비대화형 로그인을 사용합니다. GitHub Actions/GitLab CI에서 실행되는 셸 스크립트 예시입니다.
#!/usr/bin/env bash
set -euo pipefail
: "${CF_API:?}" "${CF_USER:?}" "${CF_PASSWORD:?}" "${CF_ORG:?}" "${CF_SPACE:?}"
cf api "$CF_API"
cf auth "$CF_USER" "$CF_PASSWORD"
cf target -o "$CF_ORG" -s "$CF_SPACE"
# 배포 전 스모크 테스트
npm ci
npm test
# 배포 (실패 시 즉시 롤백)
if ! cf push -f manifest.yml --strategy rolling; then
echo "[DEPLOY] rolling push failed, cancelling..."
cf cancel-deployment salesorder-api || true
exit 1
fi
# 사후 스모크
URL="https://salesorder-api-dev.cfapps.ap21.hana.ondemand.com/health"
for i in {1..10}; do
if curl -fsS "$URL" > /dev/null; then echo "OK"; exit 0; fi
sleep 5
done
echo "[DEPLOY] health check failed"; exit 2
보안 측면에서는 (1) CF_PASSWORD를 파이프라인 시크릿 저장소에서만 주입하고, (2) 로컬 ~/.cf/config.json에 남는 토큰을 CI 종료 시 cf logout으로 제거하며, (3) 서비스 키는 cf create-service-key로 앱과 분리 관리하는 것을 권장합니다.
흔한 실수와 문제 해결 FAQ
실전에서 반복적으로 마주치는 이슈를 정리합니다.
- Q1. "Staging failed: An app was not successfully detected by any available buildpack" 오류
buildpack 자동 감지 실패입니다.package.json(Node),pom.xml(Java) 같은 감지 파일이 루트에 있는지 확인하고, 확실히 하려면 manifest에buildpacks: [nodejs_buildpack]을 명시합니다. - Q2. 배포는 성공했는데 라우트 접속 시 404가 나오는 경우
포트 바인딩 문제일 가능성이 높습니다. CF는PORT환경변수를 주입하는데, 앱이 하드코딩된 3000번을 리스닝하면 라우터가 트래픽을 전달하지 못합니다. 반드시process.env.PORT를 사용해야 합니다. - Q3. 서비스 바인딩 후에도 VCAP_SERVICES가 비어 있는 경우
바인딩 후cf restage(또는 재배포)를 해야 환경변수가 재주입됩니다.cf restart만으로는 반영되지 않습니다. - Q4. "OrgSpaceVisibilityError" 또는 target 실패
Subaccount에 CF 환경이 활성화되어 있어야 하고, 사용자에게 Space Developer 역할이 할당되어야 합니다.cf orgs가 빈 값이면 권한 문제입니다. - Q5. 메모리 부족(OOM)으로 인스턴스가 반복 재시작
cf events에crashed - reason: CRASHED, exit_description: out of memory가 보이면 memory 상향(-m 512M) 또는 인스턴스 수 조정이 필요합니다.
확장 주제 및 이어서 볼 것
CLI 배포에 익숙해졌다면 다음 주제로 넓혀갈 수 있습니다.
- MTA(Multi-Target Application)와
mbt build+cf deploy조합으로 CAP 프로젝트 전체 스택 배포 cf-cli-autoscaler-plugin으로 CPU/메모리 기반 오토스케일링 규칙 코드화- SAP BTP Terraform Provider로 Subaccount·Entitlement까지 IaC 관리
- Cloud Controller v3 API를 직접 호출해 CI 서버 안에서 커스텀 배포 오케스트레이션 구현
더 읽어볼 자료
- SAP Help: Development Using Cloud Foundry
- SAP Help: Deploy Application in the Cloud Foundry Environment
- SAP Help: Download and Install the Cloud Foundry CLI
- Cloud Foundry Docs: Deploying an App
- Cloud Foundry Docs: Manifest Attributes Reference
- Cloud Foundry Docs: Rolling App Deployments
- SAP Developers: Getting Started with Cloud Foundry on BTP
댓글 0
아직 댓글이 없습니다.