BÀI VIẾT // [DEVOPS] Bash và Cloud CLI: Quản lý cloud hiệu quả với AWS, GCP, Azure
Bash và Cloud CLI: Quản lý cloud hiệu quả với AWS, GCP, Azure

Bash và Cloud CLI: Quản lý cloud hiệu quả với AWS, GCP, Azure

Hướng dẫn quản lý cloud bằng Bash và CLI tools: AWS CLI, GCP CLI, Azure CLI — authentication, query JSON output, tự động hóa EC2/VM/app service.

DEVOPS BASH

Bash và Cloud CLI trong DevOps

Trong bài viết trước, chúng ta đã tìm hiểu cách gọi REST API với curljq. Bây giờ, chúng ta sẽ áp dụng kỹ năng đó vào cloud — nơi Bash kết hợp với Cloud CLI để quản lý tài nguyên trên AWS, GCP, Azure một cách tự động và hiệu quả.

Khi quản lý cloud ở quy mô lớn, click chuột trên web console không còn khả thi. Cloud CLI cho phép bạn khởi động/tắt instance, kiểm tra trạng thái, snapshot, scale — tất cả từ terminal. Và Bash là “glue” để kết nối các CLI tools này thành automation scripts mạnh mẽ.

Bài viết này sẽ hướng dẫn bạn cách thiết lập và sử dụng AWS CLI, GCP CLI, Azure CLI trong Bash, query JSON output, và xây dựng script quản lý cloud thực tế.


Cloud CLI overview

Tại sao dùng Cloud CLI?

Phương phápƯu điểmNhược điểm
Web ConsoleTrực quan, dễ dùngChậm, không tự động được
Cloud CLINhanh, scriptable, tự độngCần học syntax từng cloud
API trực tiếpLinh hoạt nhấtPhức tạp, phải xử lý auth手动

Cloud CLI là sweet spot — đủ mạnh để tự động hóa, đủ đơn giản để học nhanh. Và Bash là công cụ hoàn hảo để orchestrate nhiều CLI tools.

Install và authentication

Mỗi cloud provider có CLI tool riêng:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# AWS CLI
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install

# GCP CLI
curl https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-linux-x86_64.tar.gz | tar xz
./google-cloud-sdk/install.sh

# Azure CLI
curl -sL https://aka.ms/InstallAzureCli | bash

Sau khi cài, mỗi cloud cần authentication:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 # AWS: cấu hình credentials
aws configure
# Nhập Access Key ID, Secret Access Key, Region, Output format

# GCP: login bằng browser
gcloud auth login
gcloud config set project <your-project-id>

# Azure: login bằng browser
az login
az account set --subscription <your-subscription-id>

Query JSON output

Tất cả cloud CLI đều support output JSON — và kết hợp với jq (từ bài trước), bạn có thể filter và transform data dễ dàng.

AWS CLI

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Liệt kê tất cả EC2 instances đang chạy
aws ec2 describe-instances \
    --query 'Reservations[].Instances[?State.Name==`running`].[InstanceId,InstanceType,PrivateIpAddress]' \
    --output table

# Dùng jq để filter chi tiết hơn
aws ec2 describe-instances | jq -r '.Reservations[].Instances[] | select(.State.Name == "running") | [.InstanceId, .InstanceType, .PrivateIpAddress] | @tsv'

# Lấy AMI mới nhất
aws ec2 describe-images --owners self \
    --query 'Images | sort_by(@, &CreationDate) | [-1].[ImageId,Name]' \
    --output text

GCP CLI

1
2
3
4
5
6
7
8
9
# Liệt kê VM instances đang chạy
gcloud compute instances list --filter="status=RUNNING" \
    --format="table(name, zone, machineType.basename(), status)"

# Lấy external IP
gcloud compute instances list --format="value(name, networkInterfaces[0].accessConfigs[0].natIP)" | awk '{print $1": "$2}'

# Query disks
gcloud compute disks list --format="json" | jq '.[] | {name, sizeGb, status}'

Azure CLI

1
2
3
4
5
6
7
8
# Liệt kê VMs đang chạy
az vm list --query "[?powerState=='VM running'].[name, resourceGroup, hardwareProfile.vmSize]" --output table

# Lấy public IP
az network public-ip list --query "[?ipConfiguration!=null].[name, ipAddress]" --output table

# Query app services
az webapp list --query "[].{name:name, state:state, plan:appServicePlan}" --output table

Ví dụ thực hành: Cloud health checker

Script kiểm tra trạng thái tài nguyên trên cả 3 cloud provider:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#!/usr/bin/env bash
# ==============================================================================
# Script : cloud-health-checker.sh
# Mục đích: Kiểm tra trạng thái tài nguyên trên AWS, GCP, Azure
# ==============================================================================

set -euo pipefail

readonly LOG_FILE="/var/log/cloud-health.log"
readonly TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
readonly SLACK_WEBHOOK="${SLACK_WEBHOOK:-}"

# Cloud provider flags (bật/tắt theo config)
readonly CHECK_AWS="${CHECK_AWS:-false}"
readonly CHECK_GCP="${CHECK_GCP:-false}"
readonly CHECK_AZURE="${CHECK_AZURE:-false}"

# AWS config
readonly AWS_REGION="${AWS_REGION:-ap-southeast-1}"
export AWS_DEFAULT_OUTPUT="json"

# GCP config
readonly GCP_PROJECT="${GCP_PROJECT:-}"

# Azure config
readonly AZURE_SUBSCRIPTION="${AZURE_SUBSCRIPTION:-}"

# Logging
log() {
    local level="$1"
    local message="$2"
    echo "[$TIMESTAMP] [$level] $message" | tee -a "$LOG_FILE"
}

# Slack notification
notify_slack() {
    local message="$1"
    if [[ -n "$SLACK_WEBHOOK" ]]; then
        curl -s -X POST -H 'Content-type: application/json' \
            --data "{\"text\":\"${message}\"}" "$SLACK_WEBHOOK" >/dev/null 2>&1
    fi
}

# AWS: Kiểm tra EC2 instances
check_aws_ec2() {
    log "INFO" "Checking AWS EC2 instances..."

    local instances
    instances=$(aws ec2 describe-instances --region "$AWS_REGION" 2>/dev/null || echo '{"Reservations":[]}')

    local running_count
    running_count=$(echo "$instances" | jq '[.Reservations[].Instances[] | select(.State.Name == "running")] | length')

    local stopped_count
    stopped_count=$(echo "$instances" | jq '[.Reservations[].Instances[] | select(.State.Name == "stopped")] | length')

    log "INFO" "AWS EC2: $running_count running, $stopped_count stopped"

    # Kiểm tra instances stopped bất thường
    local stopped_instances
    stopped_instances=$(echo "$instances" | jq -r '.Reservations[].Instances[] | select(.State.Name == "stopped") | .InstanceId')

    if [[ -n "$stopped_instances" ]]; then
        while IFS= read -r instance_id; do
            log "WARN" "Instance $instance_id is stopped"
            notify_slack "AWS Alert: Instance $instance_id is stopped"
        done <<< "$stopped_instances"
    fi
}

# GCP: Kiểm tra VM instances
check_gcp_compute() {
    log "INFO" "Checking GCP Compute instances..."

    local instances
    instances=$(gcloud compute instances list --project "$GCP_PROJECT" --format=json 2>/dev/null || echo '[]')

    local running_count
    running_count=$(echo "$instances" | jq '[.[] | select(.status == "RUNNING")] | length')

    local stopped_count
    stopped_count=$(echo "$instances" | jq '[.[] | select(.status == "TERMINATED")] | length')

    log "INFO" "GCP Compute: $running_count running, $stopped_count terminated"
}

# Azure: Kiểm tra VMs
check_azure_vms() {
    log "INFO" "Checking Azure VMs..."

    az account set --subscription "$AZURE_SUBSCRIPTION" 2>/dev/null || true

    local vms
    vms=$(az vm list --query "[].{name:name, state:powerState, group:resourceGroup}" --output json 2>/dev/null || echo '[]')

    local running_count
    running_count=$(echo "$vms" | jq '[.[] | select(.state | contains("running"))] | length')

    local stopped_count
    stopped_count=$(echo "$vms" | jq '[.[] | select(.state | contains("deallocated"))] | length')

    log "INFO" "Azure VMs: $running_count running, $stopped_count deallocated"
}

# Main
main() {
    log "INFO" "=== Cloud Health Check Started ==="

    if [[ "$CHECK_AWS" == "true" ]]; then
        check_aws_ec2
    fi

    if [[ "$CHECK_GCP" == "true" ]]; then
        check_gcp_compute
    fi

    if [[ "$CHECK_AZURE" == "true" ]]; then
        check_azure_vms
    fi

    log "INFO" "=== Cloud Health Check Completed ==="
}

main "$@"

Chạy script

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Bật check AWS và GCP
CHECK_AWS=true CHECK_GCP=true bash cloud-health-checker.sh

# Output mẫu
# [2026-09-05 10:00:00] [INFO] === Cloud Health Check Started ===
# [2026-09-05 10:00:01] [INFO] Checking AWS EC2 instances...
# [2026-09-05 10:00:02] [INFO] AWS EC2: 5 running, 1 stopped
# [2026-09-05 10:00:02] [WARN] Instance i-0987654321fedcba0 is stopped
# [2026-09-05 10:00:03] [INFO] Checking GCP Compute instances...
# [2026-09-05 10:00:04] [INFO] GCP Compute: 3 running, 0 terminated
# [2026-09-05 10:00:04] [INFO] === Cloud Health Check Completed ===

Automation tasks phổ biến

Snapshot EC2 instances (AWS)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/env bash
# Tạo snapshot cho tất cả EBS volumes attached to running instances

set -euo pipefail

REGION="${1:-ap-southeast-1}"

echo "Getting running instances in $REGION..."

# Lấy danh sách volume IDs từ instances đang chạy
VOLUME_IDS=$(aws ec2 describe-instances --region "$REGION" \
    --query 'Reservations[].Instances[?State.Name==`running`].BlockDeviceMappings[].Ebs.VolumeId' \
    --output text | tr '\t' '\n' | sort -u)

for vol_id in $VOLUME_IDS; do
    echo "Creating snapshot for volume: $vol_id"
    aws ec2 create-snapshot \
        --volume-id "$vol_id" \
        --description "Auto-snapshot $(date +%Y-%m-%d)" \
        --tag-specifications "ResourceType=snapshot,Tags=[{Key=AutoBackup,Value=true}]" \
        --region "$REGION" > /dev/null

    echo "Snapshot created for $vol_id"
done

echo "All snapshots completed"

Auto-scale VMs based on CPU (GCP)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/env bash
# Tự động scale GCP instance group khi CPU > 80%

set -euo pipefail

PROJECT="${GCP_PROJECT:-my-project}"
ZONE="${GCP_ZONE:-asia-southeast1-a}"
INSTANCE_GROUP="${INSTANCE_GROUP:-web-server-group}"
CPU_THRESHOLD=80

# Lấy CPU utilization
CPU_USAGE=$(gcloud monitoring time-series list \
    --filter='metric.type="compute.googleapis.com/instance/cpu/utilization"' \
    --interval-start-time="-PT5M" \
    --format="value(points[0].value.doubleValue)" 2>/dev/null | head -1 || echo "0")

# Convert to percentage
CPU_PERCENT=$(echo "$CPU_USAGE * 100" | bc 2>/dev/null | cut -d. -f1 || echo "0")

echo "Current CPU: ${CPU_PERCENT}%"

if (( CPU_PERCENT > CPU_THRESHOLD )); then
    echo "CPU above threshold, scaling up..."
    gcloud instance-groups managed set-autoscaling "$INSTANCE_GROUP" \
        --zone="$ZONE" \
        --project="$PROJECT" \
        --min-num-replicas=2 \
        --max-num-replicas=10 \
        --target-cpu-utilization=0.7
else
    echo "CPU normal, no action needed"
fi

Stop all VMs sau giờ làm việc (Azure)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#!/usr/bin/env bash
# Tắt tất cả VMs trong resource group (dùng cho dev/test environment)

set -euo pipefail

RESOURCE_GROUP="${1:?Usage: $0 <resource-group>}"

echo "Stopping all VMs in resource group: $RESOURCE_GROUP"

az vm stop --resource-group "$RESOURCE_GROUP" --no-wait

echo "Stop command sent. Check status with:"
echo "  az vm list -g $RESOURCE_GROUP --query '[].{name:name, state:powerState}' --output table"

Multi-cloud script pattern

Khi quản lý nhiều cloud, важно abstraction layer để script không bị lock-in:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/usr/bin/env bash
# ==============================================================================
# Abstract layer cho multi-cloud operations
# ==============================================================================

# Detect cloud provider từ instance metadata
detect_cloud() {
    if curl -s --connect-timeout 1 http://169.254.169.254/latest/meta-data/ >/dev/null 2>&1; then
        echo "aws"
    elif curl -s --connect-timeout 1 -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/ >/dev/null 2>&1; then
        echo "gcp"
    elif curl -s --connect-timeout 1 http://169.254.169.254/metadata/instance?api-version=2021-02-01 >/dev/null 2>&1; then
        echo "azure"
    else
        echo "unknown"
    fi
}

# Get instance ID theo cloud provider
get_instance_id() {
    local cloud="$1"
    case "$cloud" in
        aws)
            curl -s http://169.254.169.254/latest/meta-data/instance-id
            ;;
        gcp)
            curl -s -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/name
            ;;
        azure)
            curl -s -H "Metadata: true" "http://169.254.169.254/metadata/instance/compute/name?api-version=2021-02-01" | tr -d '"'
            ;;
    esac
}

# Usage
CLOUD=$(detect_cloud)
INSTANCE_ID=$(get_instance_id "$CLOUD")
echo "Running on $CLOUD, instance: $INSTANCE_ID"

Ghi chú triển khai

  • Security: Luôn lưu credentials trong profile files (~/.aws/credentials, ~/.config/gcloud/, ~/.azure/), không hardcode trong script. Dùng IAM roles/service accounts khi có thể.
  • Rate limiting: Cloud APIs có rate limits. AWS: 100-200 requests/second, GCP: varies, Azure: varies. Add sleep hoặc retry logic khi gọi API密集.
  • Cost awareness: Một số API calls có phí (như describe-instances trên AWS). Monitor AWS Cost Explorer hoặc equivalent để tránh bất ngờ.
  • Error handling: Cloud CLI có thể fail vì nhiều lý do (network, permission, throttle). Luôn check exit code và log chi tiết.
  • Parallel execution: Khi quản lý nhiều resources, dùng parallel execution để tăng tốc, nhưng cẩn thận rate limit.

Lời kết

Bash và Cloud CLI là combination cực kỳ mạnh mẽ trong DevOps. Từ việc query JSON output với jq, tự động hóa snapshot/scale, đến multi-cloud management — tất cả đều có thể thực hiện chỉ với Bash script.

Với pattern vendor-neutral như trên, bạn có thể viết scripts chạy trên bất kỳ cloud provider nào mà không bị lock-in.

bài tiếp theo, chúng ta sẽ khám phá Bash và Security: cách bảo mật script, validate input, manage secrets, và scan với shellcheck.

THẢO LUẬN & BÌNH LUẬN