Labels, Tags, và tổ chức tài nguyên: Chiến lược metadata
Vì sao tổ chức metadata quan trọng
Khi project tăng quy mô (1000+ resources), không thể quản lý tài nguyên chỉ bằng quy ước đặt tên. Bạn cần metadata có thể truy vấn, lọc được để:
- Theo dõi chi phí: Gán cost center cho từng resource
- Kiểm soát truy cập: Cấp quyền dựa trên thuộc tính resource
- Tự động hóa: Nhắm mục tiêu resource để update/patch dựa trên metadata
- Tuân thủ: Theo dõi thuộc tính resource theo yêu cầu phân loại dữ liệu/quy định
- Vận hành: Chuyển hướng alert, query monitoring dựa trên loại resource/chủ sở hữu
Thách thức phổ biến: Hỗn loạn khi không có chiến lược tagging nhất quán:
Google Cloud project với 500 VM:
- Một số có label "env=production"
- Một số có label "environment: prod"
- Một số KHÔNG có label môi trường
- Một số có label "owner"
- Nhiều cái không có label owner
Kết quả: Không thể truy vấn đáng tin cậy "toàn bộ resource production"Labels vs Tags: Phân biệt quan trọng
GCP có hai hệ thống hoàn toàn khác nhau cho annotation — mục đích rất khác nhau:
1. Labels (Metadata)
Labels là các cặp key-value metadata dùng cho tổ chức, truy vấn, phân bổ chi phí:
yaml
Labels trên resource:
environment: production
team: backend
cost-center: eng-2024
service: payment-api
data-sensitivity: piiĐặc điểm:
- Tối đa 63 ký tự cho mỗi key/value
- Được định nghĩa trực tiếp trên từng resource (hoặc kế thừa từ parent trong một số ngữ cảnh)
- Không phải resource riêng biệt (khác Tags)
- Miễn phí sử dụng (không có quota)
- Có thể truy vấn trong GCP Console, APIs, CLI
Cách dùng:
bash
# Lọc resources theo labels
gcloud compute instances list --filter="labels.environment:production"
# Dùng để phân bổ chi phí
gcloud billing projects describe PROJECT_ID \
--format="value(billingAccountName)" \
| xargs -I {} gcloud billing accounts export-costs --billing-account={}2. Tags (Annotation theo phân cấp)
Tags là cặp key-value theo phân cấp dùng cho ép buộc chính sách có điều kiện:
yaml
Tag bindings trên project:
environment: production # được kế thừa bởi tất cả child resources
compliance-level: high
data-residency: eu-onlyĐặc điểm:
- Tối đa 256 ký tự cho mỗi key/value
- Định nghĩa ở cấp Organization hoặc Project, được kế thừa xuống dưới
- Được quản lý như các resource riêng (Tag Key, Tag Value, Tag Binding)
- Có thể bảo vệ bằng IAM (ai được phép attach/modify)
- Có thể dùng trong IAM conditions và Organization Policies
- Cần role Tag User rõ ràng để attach
Cách dùng:
bash
# Liệt kê tag keys
gcloud resource-manager tags keys list
# Tạo tag key và value
gcloud resource-manager tags keys create "environment"
gcloud resource-manager tags values create "prod" --tag-key="environment"
# Gắn tag vào project
gcloud resource-manager tags bindings create \
--tag-key="environment" \
--tag-value="prod" \
--parent="projects/PROJECT_ID"
# Dùng trong IAM conditions
gcloud projects add-iam-policy-binding PROJECT_ID \
--member=group:devs@company.com \
--role=roles/editor \
--condition='resource.matchTag("environment", "staging")'So sánh: Labels vs Tags vs Network Tags
┌─────────────┬───────────────────────────────────────────────────────────┐
│ Thuộc tính │ Labels │ Tags │ Network Tags │
├─────────────┼─────────────────┴─────────────────┴────────────────────┤
│ Mục đích │ Metadata tổ chức │ Ép buộc policy │ Firewall routing │
│ │ │ │ (mạng cũ) │
├─────────────┼─────────────────┴─────────────────┴────────────────────┤
│ Phạm vi │ Theo resource │ Theo phân cấp │ Chỉ networking VM │
│ │ │ (org → project) │ (deprecated) │
├─────────────┼─────────────────┴─────────────────┴────────────────────┤
│ Độ dài max │ 63 ký tự │ 256 ký tự │ 63 ký tự │
├─────────────┼─────────────────┴─────────────────┴────────────────────┤
│ Quota │ Không có (free) │ Không có (free) │ 64 tags/VM │
├─────────────┼─────────────────┴─────────────────┴────────────────────┤
│ IAM-aware │ Không │ CÓ (conditions) │ Không │
├─────────────┼─────────────────┴─────────────────┴────────────────────┤
│ Phân bổ chi phí │ CÓ (billing) │ Hạn chế │ Không │
├─────────────┼─────────────────┴─────────────────┴────────────────────┤
│ Kế thừa │ Không (gắn từng)│ CÓ (tree) │ Không │
└─────────────┴───────────────────────────────────────────────────────────┘Chiến lược Labels
Thiết kế label
Chiến lược label hiệu quả phải cân bằng:
- Cardinality: Không quá nhiều giá trị duy nhất cho mỗi key
- Consistency: Cùng bộ label trên nhiều loại resource
- Queryability: Label phải truy vấn được trong GCP Console + APIs
Thiết kế label tốt:
yaml
# Labels khuyến nghị
- environment: [prod, staging, dev] # Cardinality thấp
- team: [backend, frontend, data]
- service: [api, web, batch]
- cost-center: [eng, sales, ops]
- owner: [alice@company.com, bob@company.com] # Cardinality CAO nhưng vẫn truy vấn được
# Thiết kế label kém
- timestamp: [2024-01-01, 2024-01-02, ...] # Cardinality QUÁ CAO
- random-id: [uuid-per-resource] # Không hữu ích cho lọcLabels trên các loại resource khác nhau
python
# Compute Engine instance
instance_labels = {
"environment": "production",
"team": "backend",
"service": "payment-api",
"cost-center": "eng-2024"
}
create_instance(name="payment-vm", labels=instance_labels)
# Cloud Storage bucket
bucket_labels = {
"environment": "production",
"data-sensitivity": "pii",
"cost-center": "data-2024"
}
create_bucket(name="payment-data", labels=bucket_labels)
# BigQuery dataset
dataset_labels = {
"environment": "production",
"team": "data",
"cost-center": "data-2024"
}
create_dataset(dataset_id="analytics", labels=dataset_labels)Truy vấn theo labels
bash
# Liệt kê tất cả resource production
gcloud compute instances list --filter="labels.environment=production"
# Liệt kê resource thuộc team cụ thể
gcloud compute instances list --filter="labels.team:backend"
# Bộ lọc phức tạp
gcloud compute instances list \
--filter="labels.environment=prod AND labels.team:backend"
# Dùng Cloud Asset Inventory để tìm kiếm trên nhiều loại resource
gcloud asset search-all-resources \
--scope=organizations/ORG_ID \
--query="labels.cost-center:eng-2024" \
--format="table(name,assetType)"Chiến lược Tags (theo phân cấp)
Thiết kế Tag Key
Tag keys nên đại diện cho khía cạnh tổ chức/tuân thủ:
Tag keys tốt:
- environment (prod, staging, dev)
- compliance (hipaa, pci, sox)
- data-residency (us, eu, apac)
- business-unit (sales, engineering, finance)
Tag keys kém:
- random-attribute (quá tùy hứng)
- temporary (phá vỡ mục đích của phân cấp)Phân cấp Tag Value
Organization
├── Tag Key: environment
│ ├── Value: production
│ ├── Value: staging
│ └── Value: development
│
├── Tag Key: compliance
│ ├── Value: hipaa
│ ├── Value: pci
│ └── Value: sox
│
└── Tag Key: data-residency
├── Value: us
├── Value: eu
└── Value: apacIAM policy có điều kiện với Tags
bash
# Ví dụ: Chỉ cho phép triển khai production với tag prod
# Tạo deny policy: Developer không được sửa resource prod
gcloud iam deny-policies create deny-dev-prod-access \
--location=organizations/ORG_ID \
--rules='
deny {
permissions: [
"compute.instances.delete",
"compute.instances.setMetadata"
]
principals: ["group:developers@company.com"]
deny_rule {
deny_condition {
expression: "resource.matchTag(\"environment\", \"production\")"
}
}
}
'
# Kết quả: Developer không thể xóa/sửa resource gắn environment=productionOrganization Policies có điều kiện với Tags
yaml
# Organization Policy: Ép mã hóa trên resource prod בלבד
name: organizations/ORG_ID/policies/compute.cmekResources
spec:
rules:
- enforce: true
condition:
expression: "resource.matchTag('environment', 'production')"
title: "Enforce CMEK for production"
- enforce: false
condition:
expression: "resource.matchTag('environment', 'dev')"
title: "Allow default encryption for dev"Phân bổ chi phí với Labels
Xuất dữ liệu billing có labels
python
from google.cloud import bigquery
# BigQuery dataset có dữ liệu billing export
client = bigquery.Client()
# Truy vấn chi phí theo label
query = """
SELECT
labels.key as label_key,
labels.value as label_value,
SUM(cost) as total_cost,
COUNT(*) as resource_count
FROM `project.billing.gcp_billing_export_v1_XXXXXX`
WHERE DATE(usage_start_time) >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
GROUP BY labels.key, labels.value
ORDER BY total_cost DESC
"""
results = client.query(query).result()
for row in results:
print(f"{row.label_key}={row.label_value}: ${row.total_cost:.2f} ({row.resource_count} resources)")Chargeback theo cost center
python
# Tính chi phí theo cost-center và tính chargeback cho team
billing_data = query_billing_export(
start_date=start_of_month,
end_date=end_of_month
)
cost_by_center = {}
for row in billing_data:
cost_center = row.labels.get('cost-center')
cost = row.cost
cost_by_center[cost_center] = cost_by_center.get(cost_center, 0) + cost
# Tạo invoice cho từng cost center
for cost_center, total_cost in cost_by_center.items():
team = get_team_for_cost_center(cost_center)
send_chargeback_notification(team, total_cost)Các anti-pattern cần tránh
| Anti-pattern | Vấn đề | Giải pháp |
|---|---|---|
| Labels không nhất quán | Không thể truy vấn đáng tin cậy | Định nghĩa schema label |
| Labels có cardinality cao | Tổ hợp label bùng nổ | Dùng key cardinality thấp |
| Dùng Labels thay Tags cho policy | Không ép buộc theo điều kiện được | Dùng Tags cho org policies |
| Không có governance cho labels | Labels biến thành hỗn loạn | Ép qua template tạo resource |
| Chỉ gắn labels ở một số resource | Thiếu khả năng quan sát | Bắt buộc gắn labels khi tạo |
| Labels lỗi thời | Dữ liệu cũ, quyết định sai | Làm mới labels định kỳ |
Quản lý Labels với Terraform
hcl
# Định nghĩa labels chuẩn
locals {
standard_labels = {
terraform = "true"
managed_by = "terraform"
created_date = timestamp()
}
}
# Gộp với labels riêng của resource
resource "google_compute_instance" "app" {
name = "app-vm"
labels = merge(
local.standard_labels,
{
environment = var.environment
team = var.team
service = var.service
}
)
}
# Ép labels bằng policy
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
}
}
# Validation: đảm bảo labels bắt buộc có mặt
variable "required_labels" {
type = list(string)
default = ["environment", "team", "cost-center"]
}
resource "null_resource" "label_validation" {
provisioners "local-exec" {
command = <<-EOT
python3 -c "
labels = ${jsonencode(google_compute_instance.app.labels)}
required = ${jsonencode(var.required_labels)}
missing = [k for k in required if k not in labels]
if missing:
raise ValueError(f'Missing required labels: {missing}')
"
EOT
}
}Governance cho labels
python
def validate_labels(resource_type, labels):
"""Ép schema label khi tạo resource"""
required_labels = {
"environment": ["prod", "staging", "dev"],
"team": ["backend", "frontend", "data", "infra"],
"service": None # Cho phép bất kỳ giá trị nào
}
# Kiểm tra label bắt buộc
for required_key, allowed_values in required_labels.items():
if required_key not in labels:
raise ValueError(f"Missing required label: {required_key}")
if allowed_values and labels[required_key] not in allowed_values:
raise ValueError(
f"Invalid value for {required_key}: {labels[required_key]}"
)
return True
# Ép qua GKE admission controller / OPA
# Hoặc Terraform validation blocks