Chuyển đến nội dung
MINH VO Ghi chép từ công việc
của một kỹ sư tại Việt Nam
Cơ bản7 phút đọc

Đọc cùng lúc việc thay thế và giá trị chưa biết trong Terraform plan

Đọc kế hoạch cục bộ có một ID mới kéo theo thay thế và cập nhật ở hai tài nguyên phụ thuộc, rồi kiểm tra danh tính sau khi áp dụng.

Mô hình dây chuyền băng tải với ba trạm và một nhánh vòng bên cạnh

Một kế hoạch ghi “thêm hai, thay đổi một, hủy hai” có thể mô tả thay đổi của ba thực thể tài nguyên đang tồn tại. Hai thực thể được thay thế và một được cập nhật. Số đếm tóm tắt hành động; nó không giải thích vì sao danh tính đổi hay giá trị phụ thuộc nào còn chưa biết. Hãy đọc từng thay đổi cùng đầu vào trước khi đánh giá kế hoạch chỉ qua dòng cuối.

Bài này dành cho lập trình viên đọc được khối tài nguyên Terraform nhỏ và muốn rà soát kế hoạch cẩn thận hơn. Ví dụ đã được kiểm tra bằng Terraform 1.16.1 trên Linux. Nó chỉ dùng tài nguyên tích hợp terraform_data trong thư mục làm việc cục bộ mới, không có provider bên ngoài hay provisioner. Các tài nguyên giúp quan sát vòng đời mà không tạo dịch vụ hoặc đo tính sẵn sàng.

Tạo hai cách sử dụng cùng một danh tính

Tài nguyên source lưu revision một và cũng dùng revision đó làm triggers_replace. Theo tài liệu tài nguyên tích hợp, đổi triggers_replace sẽ yêu cầu thay thế. Consumer dùng ID được sinh ra của source làm điều kiện thay thế của chính nó. Observer chỉ lưu ID đó dưới dạng đầu vào thông thường.

Cả hai tài nguyên phía sau đều phụ thuộc source qua tham chiếu giá trị tường minh. Chúng phản ứng khác nhau vì giá trị đi vào các đối số có hành vi vòng đời khác nhau. Giá trị chưa biết tự nó không phải mệnh lệnh chung yêu cầu thay thế tài nguyên nhận giá trị.

Chạy các khối lệnh sau trong cùng phiên Bash. Khối đầu tạo thư mục tạm mới và áp dụng kế hoạch gồm ba tài nguyên cục bộ. Trường annotation cố ý để null, nhằm tạo ra kế hoạch có cả null đã biết và giá trị chưa biết trong cùng một đối tượng đầu vào.

tf_plan_lab=$(mktemp -d)
cd "$tf_plan_lab"
cat > main.tf <<'HCL'
terraform {
  required_version = ">= 1.4.0, < 2.0.0"
}

resource "terraform_data" "source" {
  input            = 1
  triggers_replace = 1
}

resource "terraform_data" "consumer" {
  input = {
    source_id  = terraform_data.source.id
    annotation = null
  }
  triggers_replace = terraform_data.source.id
}

resource "terraform_data" "observer" {
  input = terraform_data.source.id
}
HCL
terraform init -input=false -no-color
terraform plan -input=false -no-color -out=initial.tfplan
terraform apply -input=false -no-color initial.tfplan
terraform show -json > before.json

Bản chụp before chứa ba ID được sinh ra. Chúng không phải số revision; ID riêng của observer cũng không phải ID của source mà observer lưu. Cần giữ sự phân biệt đó khi so sánh state cuối. Một tài nguyên có thể giữ danh tính của nó trong khi đổi tham chiếu đến tài nguyên khác.

Thay thế source làm ID mới chưa được biết; tham chiếu qua điều kiện thay thế khiến consumer được thay thế, còn tham chiếu qua input thông thường chỉ cập nhật observer.Mở ảnh kích thước đầy đủ ↗

Mũi tên mô tả sự phụ thuộc giá trị, không phải toàn bộ trình tự thực thi. Thao tác của provider và quy tắc vòng đời quyết định đồ thị công việc thực tế. Ví dụ cục bộ này không xác lập bảo đảm về thời gian gián đoạn hay tính an toàn của di chuyển dữ liệu trong dịch vụ bên ngoài.

Lưu kế hoạch và hiểu đúng mã thoát

Đổi cả hai giá trị của source sang revision hai. Các điều kiện kiểm tra khiến phép sửa chỉ phù hợp với ví dụ, tránh trường hợp thay chuỗi không tác dụng mà không ai nhận ra. Sau đó lưu kế hoạch và lấy mã thoát chi tiết trong câu điều kiện, để shell được cấu hình dừng khi có lỗi không nhầm mã 2 dự kiến với lệnh thất bại.

python3 -I - <<'PY'
from pathlib import Path
p = Path('main.tf')
s = p.read_text()
assert s.count('input            = 1') == 1
assert s.count('triggers_replace = 1') == 1
s = s.replace('input            = 1', 'input            = 2')
s = s.replace('triggers_replace = 1', 'triggers_replace = 2')
p.write_text(s)
PY
if terraform plan -input=false -no-color -detailed-exitcode -out=next.tfplan; then
  plan_status=0
else
  plan_status=$?
fi
test "$plan_status" -eq 2
terraform show -json next.tfplan > next.json

Tài liệu lệnh plan quy định: khi bật -detailed-exitcode, mã 0 là thành công và không có thay đổi, mã 1 là lỗi, mã 2 là thành công và có thay đổi. Coi mọi mã khác 0 là “đã tìm thấy thay đổi” sẽ chấp nhận cả lỗi lập kế hoạch thật. Phép kiểm tra ở đây chủ ý chờ có thay đổi; nó không phải chương trình bao quát mọi quy trình.

Kế hoạch dạng văn bản hiển thị source và consumer với -/+, nghĩa là hủy rồi tạo cho từng lần thay thế đó. Observer có ~, nghĩa là cập nhật tại chỗ. Dòng tổng kết là thêm hai, thay đổi một và hủy hai. Kế hoạch chưa thực hiện các thao tác được đề xuất. Tại thời điểm này, ví dụ vẫn có ba mối liên kết cùng ba ID ban đầu.

Đọc danh sách hành động trước khi diễn giải giá trị

Định dạng JSON được tài liệu hóa biểu diễn lần thay thế này bằng ['delete', 'create']. Thứ tự vòng đời khác có thể tạo ra ['create', 'delete']; phép kiểm tra có hành động xóa phải nhận ra cả hai. Không nên cho rằng thứ tự chữ cái trong danh sách tài nguyên được in cũng là thứ tự thực thi.

Với những thay đổi này, replace_paths chỉ ra triggers_replace là đường dẫn thuộc tính gây thay thế. Trường đó cung cấp bằng chứng hữu ích nhưng không được bảo đảm có mặt với mọi nguyên nhân thay thế. Chẳng hạn, định dạng cho phép thay thế không kèm đường dẫn thuộc tính nếu nguyên nhân không phải đối số đã đổi. Danh sách hành động đầy đủ vẫn là phát biểu trực tiếp về công việc dự kiến.

python3 -I - <<'PY'
from pathlib import Path
import json
plan = json.loads(Path('next.json').read_text())
assert plan['format_version'].split('.')[0] == '1'
changes = {item['address']: item['change']
           for item in plan['resource_changes']}
for address in sorted(changes):
    print(address, changes[address]['actions'])
source = changes['terraform_data.source']
consumer = changes['terraform_data.consumer']
observer = changes['terraform_data.observer']
assert source['actions'] == ['delete', 'create']
assert consumer['actions'] == ['delete', 'create']
assert observer['actions'] == ['update']
assert source['after']['input'] == 2
assert source['after_unknown']['id'] is True
assert consumer['after']['input']['annotation'] is None
assert consumer['after_unknown']['input']['source_id'] is True
assert observer['after']['id'] == observer['before']['id']
assert observer['after_unknown']['input'] is True
print('known revision, unknown new ID, known null, stable observer ID')
PY

Bộ đọc cố ý chỉ kiểm tra một kế hoạch nhỏ đã biết. Kiểm tra phiên bản định dạng chính giúp tránh âm thầm chấp nhận biểu diễn không tương thích, còn trường mới của phiên bản phụ được bỏ qua nếu không dùng đến. Dịch vụ rà soát tổng quát cần xử lý rộng hơn về loại tài nguyên, module, giá trị nhạy cảm và các bổ sung tương thích sau này; những điều kiện kiểm tra này không triển khai dịch vụ đó.

Chưa biết và null trả lời hai câu hỏi khác nhau

Revision hai của source đã biết lúc lập kế hoạch. ID thay thế của nó thì chưa. Trong biểu diễn thay đổi, after chứa những phần đã biết của giá trị tương lai, còn after_unknown đánh dấu các lá chưa biết bằng true. Cần kết hợp cả hai để phân biệt “giá trị sẽ được xác định sau” với một giá trị thông thường đã biết.

annotation của consumer được ghi rõ là null trong after.input. source_id của nó được đánh dấu true trong after_unknown.input, vì tài nguyên source thay thế chưa được tạo. Một phép tra cứu tiện dụng trả về None cho cả khóa vắng mặt lẫn khóa có giá trị null sẽ xóa mất khác biệt này. Không được suy ra tính chưa biết chỉ từ phép tra cứu đó; hãy đọc cấu trúc đánh dấu giá trị chưa biết theo tài liệu.

Observer là phản ví dụ cho quan niệm “chưa biết nghĩa là phải thay thế”. Đầu vào tương lai của nó chưa biết, nhưng ID riêng đã biết và không đổi. Tài nguyên tích hợp có thể cập nhật đầu vào thông thường khi danh tính mới của source xuất hiện. Consumer nhận cùng danh tính source chưa biết qua điều kiện thay thế, nên thực hiện hành động khác.

Bảng giá trị kế hoạch phân biệt revision hai đã biết, ID source mới chưa biết, annotation null đã biết, và ID observer đã biết không đổi dù input chưa biết.Mở ảnh kích thước đầy đủ ↗

Giá trị chưa biết không mặc nhiên là lỗi. Nó giới hạn những điều có thể xác lập trước khi áp dụng. Nếu phê duyệt yêu cầu một giá trị mà kế hoạch chưa xác định được, người rà soát phải ghi nhận khoảng trống đó hoặc dùng thiết kế cung cấp bằng chứng cần thiết sớm hơn. Thay vào một ID phỏng đoán không làm kế hoạch chứa thêm thông tin thật.

Áp dụng đúng tệp đã xem rồi kiểm tra danh tính

Truyền kế hoạch đã lưu vào terraform apply sẽ thực hiện kế hoạch đó mà không hỏi xác nhận lần nữa, như tài liệu apply giải thích. Hãy coi chính tệp là một phần của ranh giới phê duyệt. Chạy terraform apply không truyền tệp sẽ tạo kế hoạch mới, và những thay đổi của nó cần được rà soát riêng.

terraform apply -input=false -no-color next.tfplan
terraform show -json > after.json
python3 -I - <<'PY'
from pathlib import Path
import json
def records(filename):
    values = json.loads(Path(filename).read_text())['values']
    return {r['address']: r['values']
            for r in values['root_module']['resources']}
before, after = records('before.json'), records('after.json')
source = 'terraform_data.source'
consumer = 'terraform_data.consumer'
observer = 'terraform_data.observer'
assert before[source]['id'] != after[source]['id']
assert before[consumer]['id'] != after[consumer]['id']
assert before[observer]['id'] == after[observer]['id']
assert after[consumer]['input']['source_id'] == after[source]['id']
assert after[observer]['input'] == after[source]['id']
assert after[consumer]['input']['annotation'] is None
print('source and consumer replaced; observer identity preserved')
PY
terraform plan -input=false -no-color -detailed-exitcode

Kế hoạch cuối thoát với mã 0 và không có thay đổi. Các kiểm tra xác lập danh tính nào đã đổi và cả hai tham chiếu có trỏ tới source mới hay chưa. Chúng không khẳng định consumer trong môi trường thật sẽ kết nối lại đúng hay ứng dụng đã vượt qua kiểm tra sức khỏe. Đó là những hợp đồng tích hợp riêng không có trong ví dụ cục bộ.

Kế hoạch cũng không phải chỉ thị dùng lại vô hạn. Trong lần chạy cục bộ đã hoàn thành này, thử áp dụng lại cùng kế hoạch có thay đổi sẽ bị từ chối vì state đã tiến sang trạng thái mới. Bài thực hành sau xác nhận ranh giới quan sát được đó, thay vì coi apply thất bại là quyền ghi đè state.

if terraform apply -input=false -no-color next.tfplan > stale.log 2>&1; then
  printf '%s\n' 'Unexpected success applying the old plan' >&2
  exit 1
else
  stale_status=$?
fi
test "$stale_status" -eq 1
python3 -I - <<'PY'
from pathlib import Path
assert 'Saved plan is stale' in Path('stale.log').read_text()
print('old plan rejected after the state changed')
PY

Xem xét hạn chế trước khi khái quát kết quả

Lập kế hoạch thành công không bảo đảm lần apply sau có tính chất giao dịch. Quyền bên ngoài, hạn ngạch hoặc API có thể gây lỗi khi công việc đã bắt đầu. Terraform không tự động hoàn tác một lần apply chỉ hoàn thành một phần; tài liệu apply mô tả việc ghi nhận thay đổi đã xong và báo lỗi. Phục hồi bắt đầu bằng kiểm tra state cùng lỗi thực tế, không phải giả định mọi tài nguyên vẫn nguyên trạng.

Kế hoạch đã lưu và bản JSON của nó có thể chứa giá trị nhạy cảm dù đầu ra terminal che chúng. Cần lưu và chia sẻ phù hợp với nội dung. Ví dụ chỉ chứa giá trị giả. Tương tự, tắt refresh hoặc thu hẹp kế hoạch bằng -target có thể bỏ qua thông tin liên quan đến lần rà soát đầy đủ; tài liệu plan mô tả chúng là công cụ cho tình huống riêng, không phải đường tắt để làm một khác biệt bất tiện biến mất.

Ở bài tập tiếp theo, tạo một bản sao dùng riêng rồi bỏ đi, cho consumer chỉ lưu ID source trong input và bỏ điều kiện thay thế của nó. Dự đoán hành động trước khi chạy plan. Sau đó so sánh ID trước và sau apply. Thay đổi này kiểm tra việc hiểu hợp đồng đối số, thay vì học thuộc rằng mọi tài nguyên phụ thuộc đều phải được thay thế.

Khi hoàn tất với thư mục này, xem rồi áp dụng kế hoạch dọn dẹp. Cả ba tài nguyên còn lại đều là ví dụ cục bộ được quản lý bằng state.

terraform plan -destroy -input=false -no-color -out=cleanup.tfplan
terraform apply -input=false -no-color cleanup.tfplan
terraform state list

Danh sách cuối trống. Một lần rà soát kế hoạch hữu ích lúc này có thể nói rõ tài nguyên nào được thay thế, tham chiếu danh tính nào thay đổi, giá trị nào vẫn chưa biết và lần apply sau còn phải cung cấp bằng chứng gì.

Nguồn tham khảo & đọc thêm

  1. HashiCorp: terraform plan
  2. HashiCorp: JSON output format
  3. HashiCorp: terraform_data resource
  4. HashiCorp: terraform apply
← Về trang bài viết

Hình minh họa

100%