0Pricing
DevOps Bootcamp · 강의

CLI와 jq를 활용한 클라우드 리소스 스크립팅

클라우드 제공업체 CLI를 멱등적으로 제어하고 JSON 응답을 분석하여 리소스를 프로비저닝하고 제거합니다.

CLI와 jq를 활용한 클라우드 리소스 스크립팅은(는) CoddyKit의 무료 DevOps Bootcamp 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 DevOps Bootcamp 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. DevOps Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.

멱등적 클라우드 스크립팅이 중요한 이유

C2 수준에서 클라우드 스크립팅은 버튼을 클릭하는 것이 아니라, 중복 리소스를 만들거나 두 번째 실행에서 실패하지 않고 안전하게 여러 번 실행할 수 있는 코드를 작성하는 것입니다.

멱등적 스크립트는 리소스를 생성하기 전에 해당 리소스가 이미 존재하는지 확인합니다. 이는 신뢰할 수 있는 인프라 자동화의 초석입니다.

  • 클라우드 CLI(AWS, GCP, Azure)는 JSON을 반환하므로 이 출력을 구문 분석하는 것이 필수입니다.
  • jq는 셸 스크립트에서 JSON을 추출하고, 필터링하고, 변환하는 데 사용하는 표준 Unix 도구입니다.
  • CLI + jq + 조건부 로직을 결합하면 견고하고 반복 가능한 프로비저닝 스크립트를 작성할 수 있습니다.

이 레슨 전체에서 AWS CLI를 참조 구현으로 사용하여 S3 버킷, EC2 인스턴스 및 IAM 역할을 프로비저닝합니다. 여기서 사용하는 패턴은 gcloud와 az에도 그대로 적용됩니다.

클라우드 CLI 설치 및 확인

스크립팅 전에 필요한 도구가 있는지 확인하십시오. 환경 간 차이를 방지하려면 CI에서 항상 버전을 고정하십시오.

아래 코드 조각은 AWS CLI v2, jq 및 GCP SDK를 확인하고, 누락된 항목만 설치합니다. 새 VM이나 컨테이너용 부트스트랩 스크립트에서 유용한 패턴입니다.

#!/usr/bin/env bash
set -euo pipefail

check_or_install() {
  local cmd="$1"
  local install_cmd="$2"
  if ! command -v "$cmd" &>/dev/null; then
    echo "[INFO] $cmd not found — installing..."
    eval "$install_cmd"
  else
    echo "[OK]   $cmd $("$cmd" --version 2>&1 | head -1)"
  fi
}

# AWS CLI v2
check_or_install aws \
  'curl -fsSL https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip -o /tmp/awscliv2.zip && unzip -q /tmp/awscliv2.zip -d /tmp && sudo /tmp/aws/install'

# jq
check_or_install jq \
  'sudo apt-get install -y jq 2>/dev/null || sudo yum install -y jq'

# gcloud (optional)
check_or_install gcloud \
  'echo "Install gcloud SDK manually from https://cloud.google.com/sdk"'

echo "All prerequisites satisfied."

jq로 기존 리소스 조회

모든 멱등적 스크립트의 첫 단계는 읽기입니다. API에 리소스가 이미 존재하는지 확인한 후 그에 따라 분기합니다.

AWS CLI는 항상 JSON을 반환합니다. jq를 사용하면 필요한 필드를 정확히 추출할 수 있습니다:

  • jq -r '.Buckets[].Name' — 원시 문자열 출력으로, 줄마다 버킷 이름 하나를 출력합니다.
  • jq -e — 표현식 결과가 null 또는 false이면 종료 코드 1로 종료하므로 if 보호 조건에 적합합니다.
  • jq '.[] | select(.Name == env.BUCKET)' — env를 통해 셸 변수를 사용하여 필터링합니다.

아래 코드 조각은 모든 S3 버킷을 나열하고 대상 버킷이 이미 존재하는지 확인합니다.

#!/usr/bin/env bash
set -euo pipefail

BUCKET="my-devops-artifacts-$(date +%Y%m)"

echo "Fetching existing S3 buckets..."
EXISTING=$(aws s3api list-buckets --output json)

# Extract names as newline-separated list
echo "$EXISTING" | jq -r '.Buckets[].Name'

# Check if target bucket exists
if echo "$EXISTING" | jq -e --arg b "$BUCKET" '.Buckets[] | select(.Name == $b)' > /dev/null 2>&1; then
  echo "[EXISTS] Bucket $BUCKET already present — skipping creation."
else
  echo "[MISSING] Bucket $BUCKET not found — will create."
fi

멱등적 S3 버킷 생성

존재 확인이 마련되었으므로 생성 작업을 보호 조건으로 감싸십시오. 잘 구조화된 클라우드 함수는 다음 패턴을 따릅니다:

  1. API에서 현재 상태를 읽습니다.
  2. 원하는 상태와 실제 상태를 비교합니다.
  3. 차이가 있을 때만 작업을 수행합니다.

--create-bucket-configuration 플래그에 유의하십시오. 이 플래그는 us-east-1을 제외한 모든 리전에 필요합니다. 스크립트 내부에 리전을 하드코딩하면 AWS_DEFAULT_REGION이 설정되지 않았을 때 조용한 실패를 방지할 수 있습니다.

#!/usr/bin/env bash
set -euo pipefail

REGION="eu-west-1"
BUCKET="my-devops-artifacts-$(date +%Y%m)"

ensure_bucket() {
  local bucket="$1"
  local region="$2"

  local existing
  existing=$(aws s3api list-buckets --query 'Buckets[].Name' --output json)

  if echo "$existing" | jq -e --arg b "$bucket" 'index($b) != null' > /dev/null 2>&1; then
    echo "[SKIP] Bucket $bucket already exists."
    return 0
  fi

  echo "[CREATE] Creating bucket $bucket in $region..."
  aws s3api create-bucket \
    --bucket "$bucket" \
    --region "$region" \
    --create-bucket-configuration LocationConstraint="$region"

  # Enable versioning immediately after creation
  aws s3api put-bucket-versioning \
    --bucket "$bucket" \
    --versioning-configuration Status=Enabled

  echo "[DONE] Bucket $bucket created with versioning enabled."
}

ensure_bucket "$BUCKET" "$REGION"

중첩 JSON 구문 분석: EC2 인스턴스 상태

EC2 응답은 깊게 중첩되어 있습니다. jq 경로 순회와 --query(JMESPath, AWS CLI에 기본 제공)는 모두 작동하지만, 복잡한 로직에는 jq가 더 강력합니다.

EC2의 주요 jq 패턴:

  • .Reservations[].Instances[] — 이중 배열 구조를 평탄화합니다.
  • select(.State.Name == "running") — 상태로 필터링합니다.
  • .Tags[] | select(.Key == "Name") | .Value — 태그 값을 추출합니다.

아래 코드 조각은 Name 태그로 실행 중인 인스턴스를 찾고 해당 인스턴스의 ID와 프라이빗 IP를 반환합니다.

#!/usr/bin/env bash
set -euo pipefail

INSTANCE_NAME="web-server-prod"

RESULT=$(aws ec2 describe-instances \
  --filters \
    "Name=tag:Name,Values=${INSTANCE_NAME}" \
    "Name=instance-state-name,Values=running" \
  --output json)

# Extract instance ID and private IP using jq
INSTANCE_ID=$(echo "$RESULT" | jq -r \
  '.Reservations[].Instances[] | .InstanceId')

PRIVATE_IP=$(echo "$RESULT" | jq -r \
  '.Reservations[].Instances[] | .PrivateIpAddress')

if [[ -z "$INSTANCE_ID" ]]; then
  echo "[WARN] No running instance named '$INSTANCE_NAME' found."
  exit 1
fi

echo "Instance ID : $INSTANCE_ID"
echo "Private IP  : $PRIVATE_IP"

멱등적 IAM 역할 프로비저닝

IAM 리소스는 전역 리소스이므로 중복해서는 안 됩니다. 이미 존재하는 역할을 생성하려고 하면 AWS는 특정 오류 코드인 EntityAlreadyExists를 반환합니다. 이 코드를 포착하는 방식은 IAM을 대규모로 다룰 때 사전 확인 목록 호출보다 더 깔끔한 멱등성 패턴입니다.

아래 스크립트는 다음을 보여 줍니다:

  • || true를 사용해 CLI 종료 코드를 포착하여 set -e가 실행을 중단하지 않게 합니다.
  • 프로세스 치환을 사용하여 AWS가 표준 오류에 기록하는 오류 메시지 JSON을 구문 분석합니다.
  • 정책이 아직 연결되어 있지 않은 경우에만 정책을 연결합니다.
#!/usr/bin/env bash
set -euo pipefail

ROLE_NAME="DevOpsDeployRole"
POLICY_ARN="arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"

TRUST_POLICY='{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "ec2.amazonaws.com" },
    "Action": "sts:AssumeRole"
  }]
}'

# Attempt creation; ignore EntityAlreadyExists
CREATE_OUTPUT=$(aws iam create-role \
  --role-name "$ROLE_NAME" \
  --assume-role-policy-document "$TRUST_POLICY" \
  --output json 2>&1) || {
  if echo "$CREATE_OUTPUT" | grep -q 'EntityAlreadyExists'; then
    echo "[SKIP] Role $ROLE_NAME already exists."
  else
    echo "[ERROR] Unexpected error: $CREATE_OUTPUT" >&2
    exit 1
  fi
}

# Attach policy (attach-role-policy is idempotent by default)
aws iam attach-role-policy \
  --role-name "$ROLE_NAME" \
  --policy-arn "$POLICY_ARN"

echo "[OK] Role $ROLE_NAME ready with policy $POLICY_ARN."

jq 고급 기능: 변환, 매핑 및 toentries

실제 인프라 응답에는 수십 개의 필드가 포함됩니다. jq 변환을 사용하면 후속 도구, 로그 또는 구성 파일에 맞게 출력을 재구성할 수 있습니다.

필수 고급 패턴:

  • map(select(...)) — 배열 구조를 유지하면서 배열을 필터링합니다.
  • to_entries | map(select(.value != null)) — 구성에 기록하기 전에 null 필드를 제거합니다.
  • [.[] | {id: .InstanceId, ip: .PrivateIpAddress}] — 새로운 형태로 투영합니다.
  • @csv, @tsv, @base64 — 기본 제공 형식 변환기입니다.

아래 코드 조각은 모든 실행 중인 인스턴스를 추출하고 TSV 인벤토리 파일에 기록합니다.

#!/usr/bin/env bash
set -euo pipefail

OUTPUT_FILE="/tmp/ec2_inventory.tsv"

aws ec2 describe-instances \
  --filters "Name=instance-state-name,Values=running" \
  --output json \
| jq -r '
  ["InstanceId", "Name", "PrivateIp", "Type", "AZ"],
  [
    .Reservations[].Instances[] | [
      .InstanceId,
      (.Tags // [] | map(select(.Key == "Name")) | .[0].Value // "(none)"),
      (.PrivateIpAddress // "N/A"),
      .InstanceType,
      .Placement.AvailabilityZone
    ]
  ][]
| @tsv' > "$OUTPUT_FILE"

echo "Inventory written to $OUTPUT_FILE:"
column -t "$OUTPUT_FILE"

비동기 작업 대기: jq로 폴링

클라우드 작업은 비동기적입니다. EC2 인스턴스를 생성하면 상태가 pending인 채로 즉시 반환됩니다. 신뢰할 수 있는 스크립트는 계속 진행하기 전에 원하는 상태에 도달할 때까지 폴링해야 합니다.

아래 패턴은 지수 백오프를 사용하는 until 루프입니다. AWS CLI는 wait 하위 명령(예: aws ec2 wait instance-running)도 제공하지만, 직접 작성한 폴링을 사용하면 사용자 지정 시간 제한을 제어하고 더 풍부한 로그를 남길 수 있습니다.

#!/usr/bin/env bash
set -euo pipefail

INSTANCE_ID="i-0abcdef1234567890"
MAX_WAIT=300   # seconds
INTERVAL=10
ELAPSED=0

echo "Waiting for instance $INSTANCE_ID to reach 'running' state..."

while true; do
  STATE=$(aws ec2 describe-instances \
    --instance-ids "$INSTANCE_ID" \
    --output json \
  | jq -r '.Reservations[0].Instances[0].State.Name')

  echo "  [$(date +%T)] state = $STATE"

  [[ "$STATE" == "running" ]] && break

  if [[ "$STATE" == "terminated" || "$STATE" == "shutting-down" ]]; then
    echo "[FATAL] Instance entered terminal state: $STATE" >&2
    exit 1
  fi

  if (( ELAPSED >= MAX_WAIT )); then
    echo "[TIMEOUT] Instance did not reach 'running' after ${MAX_WAIT}s." >&2
    exit 1
  fi

  sleep "$INTERVAL"
  (( ELAPSED += INTERVAL ))
done

echo "[OK] Instance $INSTANCE_ID is running."

멀티 클라우드 패턴: GCP 및 Azure 대응 방식

멱등적인 읽기 후 작업 패턴은 다른 클라우드 CLI에도 그대로 적용됩니다. gcloud와 az는 모두 JSON을 반환하고 필터링을 지원합니다:

  • GCP: gcloud ... --format='json' — AWS에서와 정확히 같은 방식으로 jq에 파이프하십시오. 스크립트에서 프롬프트를 억제하려면 gcloud ... --quiet을 사용하십시오.
  • Azure: az ... --output json — 동일한 패턴입니다. az group exists는 일반 부울 문자열(true/false)을 반환하므로 단순한 경우에는 jq가 필요하지 않습니다.

아래 코드 조각은 동일한 보호 조건 패턴을 사용하여 Azure에서 멱등적으로 리소스 그룹을 생성하고 GCP에서 GCS 버킷을 생성하는 과정을 나란히 보여 줍니다.

#!/usr/bin/env bash
set -euo pipefail

# --- Azure: idempotent resource group ---
RG="devops-rg"
LOCATION="westeurope"

if [[ $(az group exists --name "$RG") == "true" ]]; then
  echo "[SKIP] Azure resource group $RG already exists."
else
  echo "[CREATE] Creating Azure resource group $RG..."
  az group create --name "$RG" --location "$LOCATION" --output json \
    | jq '{name: .name, location: .location, provisioningState: .properties.provisioningState}'
fi

# --- GCP: idempotent GCS bucket ---
GCS_BUCKET="gs://devops-artifacts-prod"
PROJECT="my-gcp-project"

if gcloud storage buckets describe "$GCS_BUCKET" \
     --project="$PROJECT" --format='value(name)' &>/dev/null; then
  echo "[SKIP] GCS bucket $GCS_BUCKET already exists."
else
  echo "[CREATE] Creating GCS bucket $GCS_BUCKET..."
  gcloud storage buckets create "$GCS_BUCKET" \
    --project="$PROJECT" \
    --location=EU \
    --uniform-bucket-level-access
fi

해체: 안전한 리소스 삭제

삭제 스크립트는 생성 스크립트만큼 중요합니다. 안전한 해체 작업은 다음과 같습니다:

  • 무언가를 삭제하기 전에 리소스를 나열하고 사람이 검토할 수 있도록 요약을 출력합니다.
  • --dry-run 플래그를 허용하여 작업자가 실제로 실행하지 않고 계획을 확인할 수 있게 합니다.
  • 올바른 종속성 순서로 삭제합니다(예: 보안 그룹을 삭제하기 전에 인스턴스를 종료합니다).

아래 코드 조각은 실행 전 확인 보호 조건과 함께 Env=staging 태그가 지정된 모든 EC2 인스턴스를 종료합니다.

#!/usr/bin/env bash
set -euo pipefail

DRY_RUN="${1:-}"

echo "Finding staging EC2 instances..."

INSTANCE_IDS=$(aws ec2 describe-instances \
  --filters \
    "Name=tag:Env,Values=staging" \
    "Name=instance-state-name,Values=running,stopped" \
  --output json \
| jq -r '[.Reservations[].Instances[].InstanceId] | @sh')

if [[ -z "$INSTANCE_IDS" ]]; then
  echo "[INFO] No staging instances found. Nothing to do."
  exit 0
fi

echo "Instances to terminate: $INSTANCE_IDS"

if [[ "$DRY_RUN" == "--dry-run" ]]; then
  echo "[DRY-RUN] No changes made."
  exit 0
fi

read -rp "Terminate these instances? [yes/N]: " CONFIRM
[[ "$CONFIRM" != "yes" ]] && { echo "Aborted."; exit 0; }

# shellcheck disable=SC2086
aws ec2 terminate-instances --instance-ids $INSTANCE_IDS --output json \
| jq '.TerminatingInstances[] | {id: .InstanceId, state: .CurrentState.Name}'

echo "[DONE] Termination initiated."

종단 간: 멱등적인 인프라 부트스트랩 스크립트

모두 한데 모으면, 프로덕션 수준의 부트스트랩 스크립트가 여러 리소스를 올바른 순서로 조정하고 완전히 멱등적으로 동작하며, 지속적 통합 시스템이 구문 분석할 수 있는 구조화된 로그를 출력합니다.

다음과 같은 핵심 실천 사항을 보여 줍니다.

  • 구조화된 로그 기록 — log() 도우미를 사용해 [INFO], [WARN], [ERROR] 접두사를 붙입니다.
  • 상태 파일 — 생성한 리소스 ID를 JSON 상태 파일에 기록하여 이후 실행과 해제 스크립트가 동일한 참조를 공유하도록 합니다.
  • 오류 트랩 — trap이 예기치 않은 종료를 포착하고 실패한 줄 번호를 보고합니다.
#!/usr/bin/env bash
set -euo pipefail

STATE_FILE="/tmp/infra_state.json"
REGION="eu-west-1"
BUCKET="devops-bootstrap-$(date +%Y%m)"
ROLE="BootstrapRole"

log() { echo "[$(date -u +%T)] [$1] ${*:2}"; }
trap 'log ERROR "Script failed at line $LINENO"' ERR

# Initialize state
[[ -f "$STATE_FILE" ]] || echo '{}' > "$STATE_FILE"

# --- Step 1: S3 bucket ---
EXISTING_BUCKETS=$(aws s3api list-buckets --query 'Buckets[].Name' --output json)
if echo "$EXISTING_BUCKETS" | jq -e --arg b "$BUCKET" 'index($b) != null' > /dev/null; then
  log INFO "Bucket $BUCKET exists — skipping."
else
  aws s3api create-bucket --bucket "$BUCKET" --region "$REGION" \
    --create-bucket-configuration LocationConstraint="$REGION" > /dev/null
  log INFO "Bucket $BUCKET created."
fi

# Update state file
jq --arg b "$BUCKET" '.bucket = $b' "$STATE_FILE" > /tmp/_state_tmp && mv /tmp/_state_tmp "$STATE_FILE"

# --- Step 2: IAM role ---
if aws iam get-role --role-name "$ROLE" &>/dev/null; then
  log INFO "Role $ROLE exists — skipping."
else
  aws iam create-role --role-name "$ROLE" \
    --assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"},"Action":"sts:AssumeRole"}]}' \
    --output json | jq '{RoleName: .Role.RoleName, Arn: .Role.Arn}'
  log INFO "Role $ROLE created."
fi

jq --arg r "$ROLE" '.role = $r' "$STATE_FILE" > /tmp/_state_tmp && mv /tmp/_state_tmp "$STATE_FILE"

log INFO "Bootstrap complete. State: $(cat "$STATE_FILE" | jq -c .)"

이해도 확인: jq 멱등성 보호 장치

jq를 사용한 멱등적인 클라우드 스크립트 작성에 대한 이해도를 확인해 보세요.

복습: CLI와 jq를 통한 클라우드 리소스 스크립팅

이 수업에서는 셸 스크립트, 클라우드 CLI, jq를 사용한 멱등적인 클라우드 자동화의 전체 생명 주기를 다뤘습니다.

핵심 원칙:

  • 쓰기 전에 읽기 — 항상 기존 상태를 먼저 조회하고, 차이만 반영합니다.
  • 보호 조건에 jq -e 사용 — 종료 상태 모드를 사용하여 JSON 응답에 따라 if 분기를 실행합니다.
  • 오류 코드 의미 체계 — 미리 목록을 조회하는 것보다 효율적이라면 사전에 상태를 나열하는 대신 제공업체별 오류 코드(예: EntityAlreadyExists)를 포착합니다.
  • 비동기 상태 반복 조회 — 제한 시간이 있는 until 반복문을 사용하고, 리소스가 생성 직후 바로 준비되었다고 가정하지 않습니다.
  • 상태 파일 — 리소스 ID를 공유 JSON 파일에 기록하여 스크립트의 각 단계와 해제 작업이 동일한 참조를 공유하도록 합니다.
  • 모의 실행 플래그 — 파괴적인 작업을 수행하기 전에 운영자가 안전하게 검토할 수 있도록 항상 --dry-run을 지원합니다.

이러한 패턴은 엄격한 set -euo pipefail 헤더 및 ERR 트랩과 결합되어 C2 수준의 프로덕션 인프라 스크립팅의 토대를 이룹니다.

자주 묻는 질문

“CLI와 jq를 활용한 클라우드 리소스 스크립팅” 강의는 무료인가요?

네 — “CLI와 jq를 활용한 클라우드 리소스 스크립팅” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 DevOps Bootcamp 강의 전체를 잠금 해제할 수 있습니다. DevOps Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.

“CLI와 jq를 활용한 클라우드 리소스 스크립팅”에서 뭘 배우나요?

클라우드 제공업체 CLI를 멱등적으로 제어하고 JSON 응답을 분석하여 리소스를 프로비저닝하고 제거합니다. 브라우저에서 직접 실행하는 실습 코드로 DevOps Bootcamp을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

DevOps Bootcamp을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 DevOps Bootcamp은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.

“CLI와 jq를 활용한 클라우드 리소스 스크립팅” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 DevOps Bootcamp 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 DevOps Bootcamp 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 간결한 Dockerfile 및 셸 엔트리포인트 작성
  2. envsubst와 heredoc으로 구성 템플릿 만들기
  3. CLI와 jq를 활용한 클라우드 리소스 스크립팅
  4. 상태 프로브, 준비 게이트 및 대기 루프
← DevOps Bootcamp(으)로 돌아가기