BÀI VIẾT // [DEVOPS] Bash và REST API: Gọi và xử lý API hiệu quả trong DevOps
Bash và REST API: Gọi và xử lý API hiệu quả trong DevOps

Bash và REST API: Gọi và xử lý API hiệu quả trong DevOps

Hướng dẫn gọi REST API với curl trong Bash: GET/POST/PUT/DELETE, parse JSON với jq, bearer auth, retry logic, và tự động hóa GitHub/Slack API trong DevOps.

DEVOPS BASH

Bash và REST API trong DevOps

Trong bài viết trước, chúng ta đã tìm hiểu cách tối ưu hiệu suất với Parallel Execution. Bây giờ, chúng ta sẽ kết hợp Bash với REST API — cầu nối để giao tiếp với hầu hết mọi dịch vụ hiện đại: từ GitHub, Slack, Jira, đến monitoring dashboard và CI/CD platform.

Trong DevOps, API là cách để lấy dữ liệu, kiểm tra trạng thái, hay điều khiển hệ thống từ xa. Và Bash là công cụ hoàn hảo để gọi API — curl có sẵn trên mọi server, jq parse JSON cực nhanh, và bạn có thể tích hợp vào bất kỳ script nào mà không cần cài thêm thư viện hay framework.

Bài viết này sẽ hướng dẫn bạn cách gọi API với curl, parse JSON response với jq, xử lý authentication, retry logic, và một script thực hành tự động tạo GitHub Issue khi deploy thất bại.


Gọi API với curl

Các method cơ bản

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# GET — lấy dữ liệu
curl -s "https://api.example.com/status"

# POST — tạo dữ liệu mới
curl -s -X POST \
    -H "Content-Type: application/json" \
    -d '{"title": "Bug report", "body": "Something went wrong"}' \
    "https://api.example.com/issues"

# PUT — cập nhật dữ liệu
curl -s -X PUT \
    -H "Content-Type: application/json" \
    -d '{"status": "resolved"}' \
    "https://api.example.com/issues/42"

# DELETE — xóa dữ liệu
curl -s -X DELETE "https://api.example.com/issues/42"

Headers và authentication

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Bearer token (phổ biến nhất)
curl -s -H "Authorization: Bearer $API_TOKEN" \
    "https://api.example.com/me"

# Basic auth
curl -s -u "username:password" \
    "https://api.example.com/private"

# Custom headers
curl -s -H "Accept: application/vnd.github.v3+json" \
    -H "X-Custom-Header: value" \
    "https://api.example.com/data"

Flag quan trọng

FlagÝ nghĩaKhi nào dùng
-sSilent — ẩn progress barLuôn dùng trong script
-SShow error khi dùng với -sLuôn dùng kèm -s
-fFail silently trên HTTP errorTránh parse error page
-w "%{http_code}"Xuất HTTP status codeKiểm tra response status
-o /dev/nullẩn output bodyChỉ cần check status
-LTheo dõi redirectAPI trả về 301/302
--connect-timeoutTimeout kết nốiTránh treo vô hạn
--retry 3Tự retry khi failXử lý network blip
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Ví dụ: Kiểm tra API health với timeout và retry
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
    --connect-timeout 5 --retry 3 --retry-delay 2 \
    "https://api.example.com/health")

if [[ "$HTTP_CODE" -eq 200 ]]; then
    echo "API is healthy"
else
    echo "API returned status $HTTP_CODE"
fi

Parse JSON với jq

jq là công cụ line-oriented processor cho JSON — cực kỳ mạnh mẽ khi kết hợp với curl.

Cài đặt

1
2
3
4
5
6
7
8
# Debian/Ubuntu
apt-get install -y jq

# CentOS/RHEL
yum install -y jq

# macOS
brew install jq

Các thao tác cơ bản

 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
# Trích xuất giá trị đơn giản
echo '{"name": "myapp", "version": "2.1.0"}' | jq -r '.name'
# Output: myapp

# Trích xuất nested value
echo '{"repo": {"owner": "org", "name": "api"}}' | jq -r '.repo.name'
# Output: api

# Lấy array elements
echo '{"tags": ["dev", "prod", "staging"]}' | jq -r '.tags[]'
# Output:
# dev
# prod
# staging

# Đếm phần tử array
echo '{"users": [{"id":1},{"id":2},{"id":3}]}' | jq '.users | length'
# Output: 3

# Filter array
echo '[{"name":"a","status":"ok"},{"name":"b","status":"fail"}]' | jq '[.[] | select(.status == "fail")]'
# Output: [{"name":"b","status":"fail"}]

# Transform data
echo '{"first":"John","last":"Doe"}' | jq '{fullName: "\(.first) \(.last)"}'
# Output: {"fullName": "John Doe"}

Kết hợp curl + jq

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Lấy tên repo và star count từ GitHub API
REPO_INFO=$(curl -s "https://api.github.com/repos/cli/cli")
NAME=$(echo "$REPO_INFO" | jq -r '.full_name')
STARS=$(echo "$REPO_INFO" | jq -r '.stargazers_count')
echo "$NAME has $STARS stars"

# Lấy danh sách open issues
curl -s "https://api.github.com/repos/owner/repo/issues?state=open" | \
    jq -r '.[] | "[\(.number)] \(.title)"'

# Lấy commit message mới nhất
curl -s "https://api.github.com/repos/owner/repo/commits?per_page=1" | \
    jq -r '.[0] | "\(.commit.author.name): \(.commit.message)"'

Retry logic trong Bash

Khi gọi API từ script, network có thể bị intermittent failure. Retry logic giúp script tự động thử lại:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# Hàm retry cơ bản
retry() {
    local max_attempts="$1"
    local delay="$2"
    shift 2
    local cmd=("$@")

    for ((attempt=1; attempt<=max_attempts; attempt++)); do
        if "${cmd[@]}"; then
            return 0
        fi
        echo "[WARN] Attempt $attempt/$max_attempts failed, retrying in ${delay}s..." >&2
        sleep "$delay"
    done

    echo "[ERROR] All $max_attempts attempts failed" >&2
    return 1
}

# Sử dụng
retry 3 2 curl -s "https://api.example.com/flaky-endpoint"

Retry với exponential backoff

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
retry_with_backoff() {
    local max_attempts="${1:-3}"
    local base_delay="${2:-1}"
    shift 2
    local cmd=("$@")

    for ((attempt=1; attempt<=max_attempts; attempt++)); do
        if "${cmd[@]}"; then
            return 0
        fi

        local delay=$((base_delay * (2 ** (attempt - 1))))
        echo "[WARN] Attempt $attempt/$max_attempts failed, retrying in ${delay}s..." >&2
        sleep "$delay"
    done

    return 1
}

# Sử dụng: retry 3 lần, delay 1s → 2s → 4s
retry_with_backoff 3 1 curl -s "https://api.example.com/data"

Ví dụ thực hành: Tự động tạo GitHub Issue khi deploy thất bại

  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
125
126
127
128
129
130
131
132
133
134
135
136
137
#!/usr/bin/env bash
# ==============================================================================
# Script : github-issue-on-failure.sh
# Mục đích: Tự động tạo GitHub Issue khi pipeline deploy thất bại
# ==============================================================================

set -euo pipefail

# Cấu hình
readonly GITHUB_TOKEN="${GITHUB_TOKEN:?GITHUB_TOKEN is required}"
readonly GITHUB_REPO="${GITHUB_REPO:?GITHUB_REPO is required (owner/repo)}"
readonly API_BASE="https://api.github.com/repos/${GITHUB_REPO}"
readonly SLACK_WEBHOOK="${SLACK_WEBHOOK:-}"

# Thông tin pipeline (từ CI/CD environment)
readonly PIPELINE_URL="${CI_PIPELINE_URL:-${BUILD_URL:-unknown}}"
readonly COMMIT_SHA="${CI_COMMIT_SHA:-$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')}"
readonly COMMIT_MSG="${CI_COMMIT_MESSAGE:-$(git log -1 --pretty=%B 2>/dev/null || echo 'unknown')}"
readonly DEPLOY_ENV="${DEPLOY_ENV:-production}"
readonly TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

# Headers
AUTH_HEADER="Authorization: Bearer ${GITHUB_TOKEN}"
ACCEPT_HEADER="Accept: application/vnd.github.v3+json"

# Hàm gọi GitHub API
github_api() {
    local method="$1"
    local endpoint="$2"
    local data="${3:-}"

    local args=(
        -s -f -S
        -X "$method"
        -H "$AUTH_HEADER"
        -H "$ACCEPT_HEADER"
        -H "Content-Type: application/json"
    )

    if [[ -n "$data" ]]; then
        args+=(-d "$data")
    fi

    curl "${args[@]}" "${API_BASE}${endpoint}"
}

# Kiểm tra issue đã tồn tại chưa (tránh duplicate)
check_duplicate() {
    local title="$1"
    local existing
    existing=$(github_api GET "/issues?state=open&labels=auto-deploy-failure&per_page=100" 2>/dev/null || echo "[]")

    echo "$existing" | jq -r '.[].title' | grep -qF "$title"
}

# Tạo GitHub Issue
create_issue() {
    local title="$1"
    local body="$2"

    if check_duplicate "$title"; then
        echo "[INFO] Duplicate issue found, skipping creation"
        return 0
    fi

    local payload
    payload=$(jq -n \
        --arg title "$title" \
        --arg body "$body" \
        '{
            title: $title,
            body: $body,
            labels: ["auto-deploy-failure", "ops"],
            assignees: []
        }')

    local response
    response=$(github_api POST "/issues" "$payload")

    if [[ $? -eq 0 ]]; then
        local issue_url
        issue_url=$(echo "$response" | jq -r '.html_url')
        local issue_number
        issue_number=$(echo "$response" | jq -r '.number')
        echo "[INFO] Created issue #${issue_number}: ${issue_url}"
    else
        echo "[ERROR] Failed to create issue" >&2
        return 1
    fi
}

# Gửi 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
}

# Main
main() {
    local fail_reason="${1:-Unknown failure}"

    local issue_title="[DEPLOY FAIL] ${DEPLOY_ENV}${COMMIT_SHA}$(date '+%Y-%m-%d')"
    local issue_body="## Deploy Failure Report

**Environment:** \`${DEPLOY_ENV}\`
**Commit:** \`${COMMIT_SHA}\`
**Time:** ${TIMESTAMP}
**Pipeline:** ${PIPELINE_URL}

### Commit Message
\`\`\`
${COMMIT_MSG}
\`\`\`

### Failure Reason
${fail_reason}

### Required Actions
- [ ] Check pipeline logs
- [ ] Verify deployment artifacts
- [ ] Confirm service health
- [ ] Update issue when resolved

---
*Auto-generated by deployment monitoring script*"

    echo "Creating GitHub issue for failed deploy..."
    create_issue "$issue_title" "$issue_body"

    local slack_msg="*[DEPLOY FAILURE]* ${DEPLOY_ENV}\nCommit: \`${COMMIT_SHA}\`\nReason: ${fail_reason}\nPipeline: ${PIPELINE_URL}"
    notify_slack -e "$slack_msg"
}

main "$@"

Sử dụng trong pipeline

1
2
3
4
5
6
7
8
9
# GitHub Actions example
deploy:
  script:
    - bash deploy.sh
  after_script:
    - |
      if [[ "$CI_JOB_STATUS" == "failed" ]]; then
        bash .github/scripts/github-issue-on-failure.sh "Deploy job failed"
      fi

Kết quả mẫu

1
2
Creating GitHub issue for failed deploy...
[INFO] Created issue #247: https://github.com/org/repo/issues/247

GitHub Issue được tạo với:

  • Title: [DEPLOY FAIL] production — a1b2c3d — 2026-09-03
  • Labels: auto-deploy-failure, ops
  • Body chứa environment, commit info, pipeline URL và checklist

Ghi chú triển khai

  • Token security: Luôn lưu GITHUB_TOKEN dưới dạng secret variable, không hardcode trong script. GitHub token cần scope repo để tạo issue.
  • Duplicate prevention: Script kiểm tra issue trùng lặp trước khi tạo — tránh spam khi pipeline fail nhiều lần liên tiếp.
  • jq dependency: Đảm bảo jq đã cài trên runner/CI machine. Hầu hết CI image đều có sẵn, nhưng nếu không, thêm apt-get install -y jq vào pipeline.
  • Rate limiting: GitHub API có rate limit (5000 requests/hour cho authenticated user). Script này gọi ít request nên không vấn đề, nhưng khi viết script gọi API yoğun, hãy check header X-RateLimit-Remaining.
  • Multi-platform: Script trên dùng GitHub API, nhưng bạn có thể thay thế endpoint để tạo issue trên GitLab, Jira, hay bất kỳ hệ thống issue tracking nào có REST API.

Lời kết

Bash và REST API là combination cực kỳ mạnh mẽ trong DevOps. Từ việc gọi API đơn giản với curl, parse JSON phức tạp với jq, xử lý retry logic, đến tự động tạo GitHub Issue khi deploy thất bại — tất cả đều có thể thực hiện chỉ với Bash mà không cần framework nặng.

bài tiếp theo, chúng ta sẽ khám phá Bash và Cloud CLI: cách quản lý AWS, GCP, Azure từ Bash, query JSON output, và tự động hóa cloud tasks.

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