GCP는 네트워크 태그 기반으로 방화벽을 제공합니다.
태그를 조회하여 적용된 것들을 GUI상에서 볼 수는 있지만, VM을 기반으로 연결된 방화벽 정책을 한번에 보여주는 GCP 기본 기능은 없습니다.
AWS는 VM을 선택시 해당 VM에 적용된 시큐리티 그룹을 한번에 볼 수 있습니다.
신규 구축이 아닌 기존 운영하는 곳에서 정보 수집시 방화벽이 VM별로 어떻게 설정되어 있는지 한번에 조회할 필요가 있어 스크립트를 통해 하는 방안을 작성하였습니다.
2가지 방안으로 작성하였습니다.
프로젝트 별 수집하는 방안
조직에서 프로젝트별로 수집하는 방안
프로젝트 기반이며, 입력 후 해당 프로젝트에 있는 VM의 리스트 조회 → 해당 리스트의 있는 vm에 네트워크 태그를 조회하여, 인그레스, 이그레스별로 해당 태그 정책을 정리합니다.
#!/bin/bash
# --- 설정 ---
# 프로젝트 ID를 입력받음
read -p "대상 프로젝트 ID를 입력하세요: " TARGET_PROJECT_ID
# TARGET_PROJECT_ID 입력 확인
if [[ -z "$TARGET_PROJECT_ID" ]]; then
echo "오류: 프로젝트 ID가 입력되지 않았습니다."
exit 1
fi
echo "사용할 프로젝트 ID: $TARGET_PROJECT_ID" # 입력된 값 확인
# --- 설정 끝 ---
# 출력 파일 이름 설정 (입력받은 프로젝트 ID 사용)
VM_LIST_FILE="vm_list_project_${TARGET_PROJECT_ID}.tsv"
OUTPUT_FILE="vm_firewall_report_project_${TARGET_PROJECT_ID}.csv" # CSV 형식
echo "프로젝트 '$TARGET_PROJECT_ID'의 VM 정보 및 태그 가져오는 중..."
# 1단계: 프로젝트 범위로 VM 리스트 생성
gcloud asset search-all-resources \
--scope=projects/$TARGET_PROJECT_ID \
--asset-types="compute.googleapis.com/Instance" \
--format="json" | jq -r '
.[] | [
(.name | split ("/") | last), # 0: VM 이름
.location, # 1: Zone (Location)
.createTime, # 2: 생성 시간
(.parentFullResourceName | split ("/") | last), # 3: 프로젝트 ID
try (.additionalAttributes.networkInterfaces[0].network | split("/") | last) catch "N/A", # 4: 네트워크 이름
(.additionalAttributes.internalIPs | join(";")), # 5: 내부 IP (세미콜론 구분)
(.networkTags | join(";")) # 6: 네트워크 태그 (세미콜론 구분)
] | @tsv
' > "$VM_LIST_FILE"
# VM 리스트 파일 생성 확인
if [[ ! -s "$VM_LIST_FILE" ]]; then
echo "오류: 프로젝트 '$TARGET_PROJECT_ID'에서 VM을 찾을 수 없거나 정보를 가져오는 데 실패했습니다."
exit 1
fi
echo "VM별 방화벽 규칙 상세 정보 조회 중 (프로젝트: $TARGET_PROJECT_ID)..."
# CSV 헤더 수정 (상세 정보 컬럼 추가)
echo "VM_Name,Project_ID,Network_Name,Network_Tags,Ingress_Rule_Details,Egress_Rule_Details" > "$OUTPUT_FILE"
# 생성된 VM 리스트 파일을 한 줄씩 읽기 (탭으로 구분)
while IFS=$'\t' read -r vm_name location create_time project_id network_name_from_asset internal_ips network_tags; do
# 에러 발생시 처리
if [[ "$project_id" != "$TARGET_PROJECT_ID" ]]; then
echo "경고: VM '$vm_name'의 프로젝트 ID '$project_id'가 대상 프로젝트 '$TARGET_PROJECT_ID'와 다릅니다. 건너<0xEB><0x8>니다."
continue
fi
echo "-----------------------------------------"
echo "Processing VM: $vm_name"
# --- 네트워크 URI 직접 조회 ---
echo " 네트워크 URI 직접 조회 시도..."
network_uri=$(gcloud compute instances describe "$vm_name" --zone="$location" --project="$project_id" --format='value(networkInterfaces[0].network)' 2>/dev/null)
gcloud_exit_code=$?
network_name="N/A"
if [[ $gcloud_exit_code -ne 0 ]] || [[ -z "$network_uri" ]]; then
echo " 오류 또는 빈 결과: $vm_name 의 네트워크 URI를 직접 조회하지 못했습니다."
network_uri="N/A"
else
network_name=$(basename "$network_uri")
echo " 네트워크 URI 조회 성공: $network_uri (Name: $network_name)"
fi
# --- 네트워크 URI 조회 끝 ---
# 방화벽 규칙 상세 정보 조회 로직 시작
ingress_details="N/A" # 기본값
egress_details="N/A" # 기본값
# 네트워크 태그가 있고, 네트워크 URI가 확인된 경우에만 방화벽 조회
if [[ -n "$network_tags" ]] && [[ "$network_tags" != "null" ]] && [[ "$network_uri" != "N/A" ]]; then
IFS=';' read -ra tags_array <<< "$network_tags"
tag_filter_part=""
valid_tags_found=false
for tag in "${tags_array[@]}"; do
if [[ -n "$tag" ]]; then
valid_tags_found=true
if [[ -n "$tag_filter_part" ]]; then tag_filter_part+=" OR "; fi
tag_filter_part+="targetTags.list():'$tag'"
fi
done
if [[ "$valid_tags_found" = true ]]; then
echo " 유효한 태그 발견 ($network_tags). 방화벽 규칙 상세 정보 조회 실행..."
# --- Ingress 규칙 상세 정보 조회 ---
ingress_filter="network='$network_uri' AND direction=INGRESS AND ($tag_filter_part)"
echo " Ingress Filter: $ingress_filter"
ingress_json=$(gcloud compute firewall-rules list --project="$TARGET_PROJECT_ID" --filter="$ingress_filter" --format="json")
gcloud_ingress_exit_code=$?
if [[ $gcloud_ingress_exit_code -eq 0 ]] && [[ -n "$ingress_json" ]] && [[ "$ingress_json" != "[]" ]]; then
# jq 를 사용하여 필요한 정보 추출 및 가공
ingress_details=$(echo "$ingress_json" | jq -r '
map(
"Name:" + .name +
"; Src:" + (.sourceRanges | join(",") // "any") +
"; Allow:" + ([.allowed[]? | .IPProtocol + (if .ports then ":" + (.ports | join(",")) else "" end)] | join(",") // "all") +
"; Priority:" + (.priority|tostring)
) | join(" | ") # 여러 규칙 정보를 " | " 문자로 연결
')
if [[ -z "$ingress_details" ]]; then ingress_details="None Found (JSON Parsing Error?)"; fi
else
# gcloud 명령 실패 또는 결과 없음
if [[ $gcloud_ingress_exit_code -ne 0 ]]; then
echo " ERROR: Ingress firewall list command failed (Exit code: $gcloud_ingress_exit_code)"
ingress_details="ERROR_LISTING_RULES"
else
ingress_details="None Found"
fi
fi
# --- Ingress 처리 끝 ---
# --- Egress 규칙 상세 정보 조회 ---
egress_filter="network='$network_uri' AND direction=EGRESS AND ($tag_filter_part)"
echo " Egress Filter: $egress_filter"
egress_json=$(gcloud compute firewall-rules list --project="$TARGET_PROJECT_ID" --filter="$egress_filter" --format="json")
gcloud_egress_exit_code=$?
if [[ $gcloud_egress_exit_code -eq 0 ]] && [[ -n "$egress_json" ]] && [[ "$egress_json" != "[]" ]]; then
# jq 를 사용하여 필요한 정보 추출 및 가공
egress_details=$(echo "$egress_json" | jq -r '
map(
"Name:" + .name +
"; Dest:" + (.destinationRanges | join(",") // "any") + # Egress는 destinationRanges 사용
"; Allow:" + ([.allowed[]? | .IPProtocol + (if .ports then ":" + (.ports | join(",")) else "" end)] | join(",") // "all") +
"; Priority:" + (.priority|tostring)
) | join(" | ") # 여러 규칙 정보를 " | " 문자로 연결
')
if [[ -z "$egress_details" ]]; then egress_details="None Found (JSON Parsing Error?)"; fi
else
# gcloud 명령 실패 또는 결과 없음
if [[ $gcloud_egress_exit_code -ne 0 ]]; then
echo " ERROR: Egress firewall list command failed (Exit code: $gcloud_egress_exit_code)"
egress_details="ERROR_LISTING_RULES"
else
egress_details="None Found"
fi
fi
# --- Egress 처리 끝 ---
else
echo " 유효한 태그를 찾을 수 없음."
ingress_details="No Valid Tags Found"
egress_details="No Valid Tags Found"
fi
elif [[ "$network_uri" == "N/A" ]]; then
echo " 네트워크 URI가 N/A 이므로 방화벽 조회를 종료합니다.."
ingress_details="Network Unknown"
egress_details="Network Unknown"
else
echo " 네트워크 태그가 없으므로 방화벽 조회를 종료합니다."
ingress_details="No Tags"
egress_details="No Tags"
fi
echo " 조회된 Ingress 규칙 상세: $ingress_details"
echo " 조회된 Egress 규칙 상세: $egress_details"
# 결과 CSV 파일에 추가 (상세 정보 컬럼 포함, 네트워크 이름은 basename 사용)
printf "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n" \
"$vm_name" "$project_id" "$network_name" "$network_tags" "$ingress_details" "$egress_details" >> "$OUTPUT_FILE"
done < "$VM_LIST_FILE"
echo "-----------------------------------------" # 구분선 추가
echo "완료! 결과가 $OUTPUT_FILE 에 저장되었습니다."
rm "$VM_LIST_FILE" # 임시 파일 삭제
동작예시
- 아래와 같이 iap 네트워크 태그가 적용된 2개의 정책이 있습니다.
각 네트워크도 다릅니다.

스크립트 실행시
해당 프로젝트에 있는 VM별로 방화벽 정책을 확인할 수 있습니다.
해당 프로젝트에 있는 VM별로 방화벽 정책을 확인할 수 있습니다.
해당 내용은 csv 파일로 내보내기되며, 아래와 같이 VM명, 프로젝트, 네트워크, 네트워크태그, 각 룰셋의 세부내용 (출발지, 포트, 우선순위)를 직관적으로 확인할 수 있습니다.

- 조직레벨에서 수집하기
프로젝트가 몇 개 없다면 위와 같이 진행해도 무방하나, 폴더, 프로젝트가 많은 경우 조직ID를 입력 후 전체 프로젝트를 조회하는 것이 더 효율적입니다.
gcloud와 쉘스크립트를 통한 방법입니다.
2025.04.18 기준 테스트시 폴더 하위에 있는 프로젝트는 적용되지 않아 수정하였습니다.
gcloud projects list -> gcloud asset search-all-resources 로 변경하여 조직내 모든 프로젝트를 조회하도록 변경했습니다.
조회 성공시 아래와 같이 출력됩니다.
#!/bin/bash # ============================================================================== # Script: GCP Organization Firewall Report (VM-based) - 최종 수정 버전 v3 # Description: # 조직 내 모든 활성 프로젝트(폴더 포함)를 검색하여 VM과 연관된 방화벽 규칙 보고 # Zone 추출 로직 개선, Dir 필드 제거, 규칙 줄바꿈 적용됨. # Requirements: jq, gcloud, 권한(Cloud Asset Viewer 등), API(Asset, Compute 등) # ============================================================================== # --- Initial Checks --- if ! command -v jq &> /dev/null; then echo "오류: 'jq'가 설치되어 있지 않습니다. 설치해주세요." exit 1 fi echo "jq 확인 완료." # --- 설정 --- read -p "대상 조직 ID를 입력하세요 (예: 1234567890): " ORGANIZATION_ID if [[ -z "$ORGANIZATION_ID" ]]; then echo "오류: 조직 ID 미입력."; exit 1; fi echo "사용할 조직 ID: $ORGANIZATION_ID" # --- 파일 이름 설정 --- PROJECT_VM_LIST_FILE="vm_list_temp_${ORGANIZATION_ID}.tsv" ORG_OUTPUT_FILE="org_firewall_report_${ORGANIZATION_ID}_$(date +%Y%m%d_%H%M%S).csv" # --- 프로젝트 목록 조회 (Asset Inventory) --- echo "조직 '$ORGANIZATION_ID' 내 모든 활성 프로젝트 목록 가져오는 중 (폴더 포함)..." project_list_json=$(gcloud asset search-all-resources \ --scope="organizations/$ORGANIZATION_ID" \ --asset-types="cloudresourcemanager.googleapis.com/Project" \ --query='state="ACTIVE"' \ --format="json" --quiet) if [[ $? -ne 0 ]]; then echo "오류: 프로젝트 목록 조회 실패. Cloud Asset API 활성화 및 권한 확인 필요." rm "$PROJECT_VM_LIST_FILE" 2>/dev/null exit 1 fi if [[ -z "$project_list_json" ]] || [[ "$(echo "$project_list_json" | jq 'length')" -eq 0 ]]; then echo "정보: 활성 프로젝트 없음." echo "VM_Name,Project_ID,Project_Name,Network_Name,Network_Tags,Ingress_Rule_Details,Egress_Rule_Details" > "$ORG_OUTPUT_FILE" echo "빈 보고서 파일 '$ORG_OUTPUT_FILE' 생성됨." rm "$PROJECT_VM_LIST_FILE" 2>/dev/null exit 0 fi # --- 프로젝트 정보 파싱 --- declare -A project_names while IFS= read -r project_json || [[ -n "$project_json" ]]; do if [[ -z "$project_json" ]]; then continue; fi p_id=$(echo "$project_json" | jq -r '.additionalAttributes.projectId // empty') p_name=$(echo "$project_json" | jq -r '.displayName // empty') state=$(echo "$project_json" | jq -r '.state // empty') if [[ "$state" != "ACTIVE" ]] || [[ -z "$p_id" ]]; then continue; fi if [[ -z "$p_name" ]]; then p_name="$p_id"; fi if [[ -z "${project_names[$p_id]}" ]]; then project_names["$p_id"]="$p_name"; fi done < <(echo "$project_list_json" | jq -c '.[]') if [[ ${#project_names[@]} -eq 0 ]]; then echo "정보: 처리할 활성 프로젝트 없음 (파싱/필터링 결과)." echo "VM_Name,Project_ID,Project_Name,Network_Name,Network_Tags,Ingress_Rule_Details,Egress_Rule_Details" > "$ORG_OUTPUT_FILE" echo "빈 보고서 파일 '$ORG_OUTPUT_FILE' 생성됨." rm "$PROJECT_VM_LIST_FILE" 2>/dev/null exit 0 fi echo "조회 및 필터링된 활성 프로젝트 수: ${#project_names[@]}" echo "조직 전체 VM별 방화벽 규칙 상세 정보 조회 시작..." # --- CSV 헤더 작성 --- echo "VM_Name,Project_ID,Project_Name,Network_Name,Network_Tags,Ingress_Rule_Details,Egress_Rule_Details" > "$ORG_OUTPUT_FILE" echo "VM_Name,Project_ID,Project_Name,Network_Name,Network_Tags,Ingress_Rule_Details,Egress_Rule_Details" # 화면 출력 # --- 메인 루프 (프로젝트별 처리) --- processed_project_count=0 total_projects=${#project_names[@]} for project_id in "${!project_names[@]}"; do project_name="${project_names[$project_id]}" ((processed_project_count++)) echo "=========================================" echo "Processing Project [$processed_project_count/$total_projects]: $project_name ($project_id)" echo "=========================================" # [1/4] Compute API 확인 echo " [1/4] Checking Compute Engine API status..." if ! gcloud services list --project="$project_id" --enabled --filter="NAME:compute.googleapis.com" --format='value(NAME)' --quiet 2>/dev/null | grep -q "compute.googleapis.com"; then echo " 정보: Compute Engine API 비활성화 또는 조회 권한 없음." sleep 0.5; continue fi echo " Compute Engine API 확인됨." # [2/4] VM 목록 조회 (Asset Inventory) echo " [2/4] Fetching VM list..." vm_list_json=$(gcloud asset search-all-resources \ --scope=projects/$project_id \ --asset-types="compute.googleapis.com/Instance" \ --format="json" --quiet 2>/dev/null) if [[ $? -ne 0 ]]; then echo " 오류: VM 목록 조회 실패. 권한(cloudasset.assets.searchAllResources) 확인 필요." >&2 sleep 0.5; continue fi # VM 목록 파싱하여 임시 파일에 저장 echo "$vm_list_json" | jq -r ' .[] | select(.state == "RUNNING" or .state == "TERMINATED") | [ (.name | split ("/") | last), # VM 이름 .location, # Location (Zone 정보 포함 가능) (.parentFullResourceName | split ("/") | last), # 프로젝트 ID 검증용 (.additionalAttributes.internalIPs // [] | join(";")), # 내부 IP (.networkTags // [] | join(";")) # 네트워크 태그 ] | @tsv ' > "$PROJECT_VM_LIST_FILE" if [[ ! -s "$PROJECT_VM_LIST_FILE" ]]; then echo " 정보: 처리할 VM (RUNNING/TERMINATED) 없음." sleep 0.5; continue fi echo " VM 목록 임시 저장 완료 ($(wc -l < "$PROJECT_VM_LIST_FILE") 개 VM)." # [3/4] VM별 네트워크/방화벽 조회 echo " [3/4] Processing VMs for network/firewall rules..." while IFS=$'\t' read -r vm_name location proj_id_check internal_ips network_tags || [[ -n "$vm_name" ]]; do # 빈 줄 건너뛰기 if [[ -z "$vm_name" ]]; then continue; fi # 프로젝트 ID 재확인 if [[ "$proj_id_check" != "$project_id" ]]; then echo " 경고: VM '$vm_name' 프로젝트 ID 불일치 ($proj_id_check != $project_id)." >&2; continue fi echo "-----------------------------------------" echo " Processing VM: $vm_name" # 변수 초기화 network_uri="N/A"; network_name="N/A"; ingress_details="N/A"; egress_details="N/A"; zone="" # --- Zone 정보 추출 로직 개선 --- if [[ "$location" == *"/zones/"* ]]; then zone=$(basename "$location") echo " Zone extracted from path: $zone" elif [[ -n "$location" ]] && [[ "$location" =~ ^[a-z]+[0-9]?-[a-z]+[0-9]+-[a-z]$ ]]; then zone="$location" echo " Using location directly as zone: $zone" else echo " 경고: Location ('$location')에서 Zone 정보를 식별할 수 없습니다." fi # --- Zone 정보 추출 로직 끝 --- # Zone 정보가 성공적으로 추출되었는지 최종 확인 if [[ -z "$zone" ]]; then echo " 오류: 최종 Zone 정보를 확정할 수 없어 네트워크/방화벽 조회를 중단합니다." >&2 network_uri="N/A (Zone Unknown)" network_name="N/A (Zone Unknown)" ingress_details="N/A (Zone Unknown)" egress_details="N/A (Zone Unknown)" else # --- 네트워크 URI 조회 (gcloud compute instances describe 사용) --- echo " Querying network URI using zone: $zone" temp_network_uri=$(gcloud compute instances describe "$vm_name" --zone="$zone" --project="$project_id" --format='value(networkInterfaces[0].network)' 2>/dev/null) gcloud_describe_exit_code=$? if [[ $gcloud_describe_exit_code -eq 0 ]] && [[ -n "$temp_network_uri" ]]; then network_uri="$temp_network_uri" network_name=$(basename "$network_uri") # URI에서 네트워크 이름만 추출 echo " Network URI found: $network_name" else echo " 경고: VM '$vm_name' (Zone: $zone)의 네트워크 URI를 조회하지 못했습니다 (Exit Code: $gcloud_describe_exit_code). VM 상태 또는 권한(compute.instances.get)을 확인하세요." >&2 network_uri="N/A (Describe Failed)" network_name="N/A (Describe Failed)" ingress_details="N/A (Describe Failed)" egress_details="N/A (Describe Failed)" fi # --- 네트워크 URI 조회 끝 --- fi # Zone 확인 끝 # --- 방화벽 규칙 조회 (네트워크 URI가 정상이고, 태그가 있을 때만) --- if [[ -n "$network_tags" ]] && [[ "$network_tags" != "null" ]] && [[ "$network_uri" != N/A* ]]; then echo " VM Tags: [$network_tags]. Querying firewall rules..." # 태그 필터 생성 IFS=';' read -ra tags_array <<< "$network_tags" tag_filter_part=""; valid_tags_found=false for tag in "${tags_array[@]}"; do if [[ -n "$tag" ]]; then valid_tags_found=true if [[ -n "$tag_filter_part" ]]; then tag_filter_part+=" OR "; fi escaped_tag=$(echo "$tag" | sed "s/'/'\\\\''/g; s/\"/\\\\\"/g") tag_filter_part+="targetTags.list():\"$escaped_tag\"" fi done # 유효한 태그가 있을 경우 방화벽 조회 if [[ "$valid_tags_found" = true ]]; then # --- Ingress 조회 (수정됨: Dir 제거, join("\n") 사용) --- ingress_filter="network=\"$network_uri\" AND direction=INGRESS AND ($tag_filter_part)" ingress_json=$(gcloud compute firewall-rules list --project="$project_id" --filter="$ingress_filter" --format="json" --quiet 2>/dev/null) if [[ $? -ne 0 ]]; then echo " 오류: Ingress 방화벽 규칙 조회 실패. 권한(compute.firewalls.list) 확인 필요." >&2 ingress_details="ERROR_LISTING_RULES" elif [[ -n "$ingress_json" ]] && [[ "$ingress_json" != "[]" ]]; then # jq: map 각 규칙을 포맷팅하고, 결과를 개행문자(\n)로 연결 ingress_details=$(echo "$ingress_json" | jq -r 'map( "Name:"+.name+ "; Src:"+(.sourceRanges | join(",") // "any")+ "; Action:"+(if .denied then "DENY" else "ALLOW" end)+ "; Proto:"+(.allowed // .denied | map(.IPProtocol + (if .ports then ":" + (.ports | join(",")) else "" end)) | join(",") // "all")+ "; Priority:"+(.priority|tostring)+ "; Enabled:"+(if .disabled then "false" else "true" end) )|join("\n")' 2>/dev/null) # join 구분자를 "\n"으로 변경 if [[ $? -ne 0 ]] || [[ -z "$ingress_details" ]]; then ingress_details="Matched Rules Found (Parsing Error)"; fi else ingress_details="None Found Matching Tags" fi # --- Egress 조회 (수정됨: Dir 제거, join("\n") 사용) --- egress_filter="network=\"$network_uri\" AND direction=EGRESS AND ($tag_filter_part)" egress_json=$(gcloud compute firewall-rules list --project="$project_id" --filter="$egress_filter" --format="json" --quiet 2>/dev/null) if [[ $? -ne 0 ]]; then echo " 오류: Egress 방화벽 규칙 조회 실패. 권한(compute.firewalls.list) 확인 필요." >&2 egress_details="ERROR_LISTING_RULES" elif [[ -n "$egress_json" ]] && [[ "$egress_json" != "[]" ]]; then # jq: map 각 규칙을 포맷팅하고, 결과를 개행문자(\n)로 연결 egress_details=$(echo "$egress_json" | jq -r 'map( "Name:"+.name+ "; Dest:"+(.destinationRanges | join(",") // "any")+ "; Action:"+(if .denied then "DENY" else "ALLOW" end)+ "; Proto:"+(.allowed // .denied | map(.IPProtocol + (if .ports then ":" + (.ports | join(",")) else "" end)) | join(",") // "all")+ "; Priority:"+(.priority|tostring)+ "; Enabled:"+(if .disabled then "false" else "true" end) )|join("\n")' 2>/dev/null) # join 구분자를 "\n"으로 변경 if [[ $? -ne 0 ]] || [[ -z "$egress_details" ]]; then egress_details="Matched Rules Found (Parsing Error)"; fi else egress_details="None Found Matching Tags" fi else echo " No valid tags found in tag field." ingress_details="No Valid Tags Found"; egress_details="No Valid Tags Found" fi elif [[ "$network_uri" == N/A* ]]; then echo " Skipping firewall rule check due to network lookup failure." else # 네트워크 URI는 정상이지만 태그가 없는 경우 echo " VM has no network tags. Skipping firewall rule check." ingress_details="No Tags on VM"; egress_details="No Tags on VM" fi # 방화벽 규칙 조회 끝 # [4/4] 결과 CSV 파일에 추가 (printf와 >> 사용) # printf는 %s 내의 개행문자(\n)를 그대로 유지하여 큰따옴표로 감싸진 필드 내 줄바꿈을 만듦 printf "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n" \ "$(echo "$vm_name" | sed 's/"/""/g')" \ "$(echo "$project_id" | sed 's/"/""/g')" \ "$(echo "$project_name" | sed 's/"/""/g')" \ "$(echo "$network_name" | sed 's/"/""/g')" \ "$(echo "$network_tags" | sed 's/"/""/g')" \ "$(echo "$ingress_details" | sed 's/"/""/g')" \ "$(echo "$egress_details" | sed 's/"/""/g')" >> "$ORG_OUTPUT_FILE" sleep 0.1 # API 할당량 및 부하 감소 done < "$PROJECT_VM_LIST_FILE" # 현재 프로젝트의 VM 목록 처리 완료 echo " [4/4] Project $project_name ($project_id) VM processing finished." done # 모든 프로젝트 처리 완료 (외부 루프 끝) # --- 최종 정리 --- echo "=========================================" echo "모든 프로젝트 처리 완료! 최종 결과가 '$ORG_OUTPUT_FILE' 에 저장되었습니다." if rm "$PROJECT_VM_LIST_FILE" 2>/dev/null; then echo "임시 파일 ($PROJECT_VM_LIST_FILE) 삭제 완료." else echo "정보: 임시 파일 ($PROJECT_VM_LIST_FILE) 삭제 불가 또는 없음." fi echo "스크립트 실행 완료." exit 0

'Cloud > GCP' 카테고리의 다른 글
GCP Asset Inventory로 내부 자산인 GCE(VM) 확인하기 (0) | 2025.04.09 |
---|---|
[GCP] STS(Storage Transfer Service) Agent 설치, 설정 (0) | 2025.03.14 |
On-premise 환경에서 GCP의 Private Google Access(PGA) 사용하기 – DNS Forward 방안 (2) | 2025.03.03 |
On-premise 환경에서 GCP의 Private Google Access(PGA) 사용하기 – DNS 설정 가이드 (0) | 2025.02.26 |
[GCP] Cloud Site to Site VPN을 위한 strongswan(openvpn), Cloud VPN 설정방안 (4) | 2025.02.25 |
GCE, GCS, Bigquery Audit Log 확인 (0) | 2025.02.18 |
[GCP] 2단계(MFA) 인증을 조직 단위로 설정하기 (0) | 2025.02.09 |