Giới thiệu
Trong quá trình phân tích và tối ưu hiệu suất của pipeline CI/CD cho một dự án hiện tại, tôi đã gặp phải một số vấn đề bất ngờ với Terraform. Sau khi tìm kiếm trên Google mà không tìm được thông tin hữu ích, tôi quyết định viết bài này để chia sẻ kinh nghiệm của mình. Trong bài viết này, tôi sẽ giải thích cách tôi xác định lý do tại sao Terraform không sử dụng các providers đã được lưu cache và cách tránh các vấn đề liên quan đến các hash cụ thể theo nền tảng trong tệp khóa provider của Terraform.
Chúng tôi đang sử dụng một instance Gitlab riêng tư để lưu trữ mã và có một runner chuyên dụng để thực thi pipeline. Phần Terraform của pipeline chịu trách nhiệm triển khai các thay đổi về mã và hạ tầng, bao gồm hai giai đoạn với các job riêng - plan và apply. Nếu bạn đã từng sử dụng Terraform, bạn có lẽ đã đoán ra rằng job plan tạo ra một tệp kế hoạch Terraform, tức là sự khác biệt giữa trạng thái hiện tại và trạng thái mục tiêu. Job apply tiếp theo sẽ tiêu thụ tệp đó và thực hiện các thay đổi (trừ khi có điều gì đó khác đã tác động đến trạng thái trong thời gian đó).
Cấu trúc Pipeline
Pipeline sử dụng các hình ảnh không có Terraform được cài đặt, vì vậy mỗi job đầu tiên sẽ cài đặt Terraform và sau đó chạy terraform init để thiết lập backend và các providers. Chỉ khi hai bước này hoàn thành, môi trường mới được thiết lập hoàn toàn và chúng tôi có thể chạy terraform plan/apply.
Dự án này chủ yếu triển khai tài nguyên trên AWS, vì vậy mã của chúng tôi phụ thuộc vào AWS provider. Thật không may, provider này có kích thước khá lớn. Phiên bản hiện tại của provider được trích xuất từ Terraform registry nặng khoảng 700MB. Dạng nén, thực tế được tải xuống, khoảng 150-180MB. Việc tải xuống này cho mỗi job sẽ tốn khá nhiều thời gian. Ngay cả trên một kết nối nhanh, việc này cũng sẽ mất ít nhất vài giây. Thời gian giải nén cũng có thể mất một chút thời gian tùy thuộc vào môi trường xây dựng của bạn.
Trong trường hợp của chúng tôi, terraform init bao gồm nhiều hơn chỉ mỗi AWS provider mất khoảng 3-4 phút mà không có cache. Điều này có nghĩa là mất khoảng 6-8 phút thời gian bổ sung cho mỗi lần chạy, điều này là không chấp nhận được. Thực ra, không có caching có vẻ không công bằng - chúng tôi đã cấu hình cache cho thư mục .terraform, nhưng nó chỉ không hoạt động. Thực tế, nó thực sự đã hoạt động - thư mục đã được cache và khôi phục giữa các lần chạy, nhưng nó không tạo ra sự khác biệt, điều này thật kỳ lạ.
Dưới đây là cấu hình caching gần giống như những gì chúng tôi đang sử dụng. Nó được đính kèm vào cả job plan và apply và tạo một cache riêng cho mỗi giai đoạn triển khai (ví dụ: dev/prod). Chúng tôi có thể đã bao gồm tệp .terraform.lock.hcl như một phần của khóa cache ở đây, cái này định nghĩa phiên bản nào của provider sẽ được sử dụng, nhưng sẽ nói về điều đó sau.
yaml
cache:
- key: 'terraform-init-$STAGE'
paths:
- .terraform
Xác định Vấn đề
Trong một thời gian dài, tôi đã nghĩ rằng cache của chúng tôi đã làm hỏng các tệp để mà lệnh init không thể nhìn thấy chúng, nhưng sau khi đăng nhập vào runner, tôi thấy rằng quyền truy cập là hoàn toàn ổn. Bước tiếp theo là tăng cường mức độ log, điều này tôi đã làm bằng cách thiết lập biến môi trường TF_LOG trước lệnh init.
bash
TF_LOG=TRACE terraform init ...
Dưới đây là đầu ra rút gọn và có chú thích. Tôi đã xóa các dấu thời gian và một số thông điệp không liên quan. Vâng, chúng tôi đang sử dụng phiên bản mới hơn của provider bây giờ. Tôi gợi ý bạn nên tập trung vào các chú thích.
plaintext
// TF quét thư mục provider địa phương và tìm thấy phiên bản mong muốn
[TRACE] getproviders.SearchLocalDirectory: found registry.terraform.io/hashicorp/aws v6.4.0 for linux_amd64 at .terraform/providers/registry.terraform.io/hashicorp/aws/6.4.0/linux_amd64
// TF đăng ký điều này như một ứng cử viên cho aws provider
[TRACE] providercache.fillMetaCache: including .terraform/providers/registry.terraform.io/hashicorp/aws/6.4.0/linux_amd64 as a candidate package for registry.terraform.io/hashicorp/aws 6.4.0
// TF quyết định cài đặt cùng một provider từ internet
// thay vì sử dụng nhị phân đã có
[TRACE] providercache.Dir.InstallPackage: installing registry.terraform.io/hashicorp/aws v6.4.0 from https://releases.hashicorp.com/terraform-provider-aws/6.4.0/terraform-provider-aws_6.4.0_linux_amd64.zip
Tóm lại, Terraform đã thấy provider địa phương phù hợp với phiên bản mong muốn nhưng vẫn quyết định tải xuống lại. Điều này không hợp lý với tôi và vẫn không hợp lý. Tôi đã đi xuống một con đường so sánh các checksum của các nhị phân đã tải xuống và cố gắng tìm hiểu xem GitLab có thay đổi chúng hoặc siêu dữ liệu của chúng hay không. Tất nhiên là không - điều đó sẽ là một triển khai caching kỳ lạ - nhưng tôi phải chắc chắn.
Tôi đã có thể thu hẹp vấn đề hơn nữa khi tôi quyết định chạy terraform init một lần nữa như một phần của cùng một job. Lần chạy thứ hai đã sử dụng phiên bản cached từ lần init đầu tiên và hoàn thành gần như tức thì. Xem xét kỹ hơn đầu ra đã khiến tôi nhận ra rằng lần chạy đầu tiên đã in ra một cái gì đó mà lần thứ hai không:
Terraform đã thực hiện một số thay đổi đối với các lựa chọn phụ thuộc của provider được ghi lại trong tệp .terraform.lock.hcl. Vui lòng xem xét những thay đổi đó và cam kết chúng vào hệ thống kiểm soát phiên bản của bạn nếu chúng đại diện cho những thay đổi mà bạn dự định thực hiện.
Tôi đã đề cập đến .terraform.lock.hcl một cách ngắn gọn trước đây, giờ là lúc đi sâu vào nó và chức năng của nó. Tệp khóa phụ thuộc là một trong hai cơ chế tham gia vào quyết định phiên bản chính xác của các provider sẽ được cài đặt. Tại thời điểm viết bài này (Terraform v1.13.x), tệp này giữ dấu vết của phiên bản chính xác của một provider đã được sử dụng và bao gồm các hash để xác minh rằng nhị phân đúng đã được cài đặt.
Khi bạn chạy terraform init, nó kiểm tra các ràng buộc phiên bản trên cấu hình provider và cũng là .terraform.lock.hcl. Nếu có một phiên bản của provider thỏa mãn các ràng buộc và đã được đề cập trong tệp khóa, nó sẽ cài đặt phiên bản đó (trừ khi bạn chỉ định tham số -upgrade). Nếu không, nó sẽ chọn một phiên bản phù hợp với các ràng buộc và cập nhật tệp khóa.
Trong trường hợp của tôi, cả cấu hình provider đều có ràng buộc phiên bản cho v6.4.0 và tệp khóa cũng bao gồm v6.4.0. Điều đó có nghĩa là tôi đã mong đợi nó sẽ lấy phiên bản địa phương từ cache - nhưng nó đã không làm vậy. Là một bước khắc phục sự cố nữa, tôi đã bao gồm tệp khóa như một artifact của job plan. Điều này đã khiến job apply sử dụng cache như tôi mong đợi, vì vậy tôi đã đi đúng hướng. Tôi đã tải tệp xuống và xem xét nó. So với phiên bản của tôi trên máy tính, pipeline đã thêm một giá trị hash khác cho AWS provider.
Sau đó, tôi nhận ra. Các provider là các nhị phân được biên dịch go và môi trường phát triển của tôi là một VM Windows trong khi pipeline xây dựng được hỗ trợ bởi các container Linux. Khi tôi nâng cấp lên v6.4.0 vài tháng trước, tôi đã làm điều đó trên máy Windows và rõ ràng nó chỉ thêm các hash cho phiên bản Windows của provider. Thực tế, có một lệnh cụ thể terraform providers lock mà bạn có thể sử dụng để thêm các hash của các nền tảng khác nhau vào tệp khóa.
Tôi đã sử dụng lệnh sau trên VM phát triển của mình để thêm các hash của các phiên bản Windows và Linux x86 của các provider.
bash
tf.providers.lock \
-platform=windows_amd64 \ # Windows 64-bit
-platform=linux_amd64 # Linux 64-bit
Lệnh này đã chạy trong một thời gian, vì nó không chỉ tải xuống siêu dữ liệu từ registry, mà thực sự tải xuống cả hai provider, tính toán các checksum và thêm chúng vào tệp terraform.lock.hcl. Tôi chắc chắn rằng đối với các provider nhỏ, điều này được thực hiện trong nháy mắt, nhưng đối với các provider lớn hơn, chẳng hạn như AWS provider, điều này mất một thời gian. Vấn đề này không chỉ xảy ra với Windows và Linux. Bất cứ khi nào nhóm phát triển của bạn sử dụng các nền tảng khác nhau, bạn có thể gặp phải điều này. Hãy xem tài liệu tôi đã liên kết cho tất cả các nền tảng được hỗ trợ.
Khi tôi đã cam kết tệp khóa được cập nhật, thời gian chạy job đã giảm đáng kể và caching đã hoạt động như tôi mong đợi. Các giai đoạn cài đặt và init bây giờ mất chưa đến 20 giây, điều này giúp tăng tốc mỗi lần chạy pipeline (trừ khi các cache bị xóa).
Những thực tiễn tốt nhất
- Thường xuyên kiểm tra tệp
.terraform.lock.hcl: Đảm bảo rằng nó luôn được cập nhật với các hash cần thiết cho mọi nền tảng mà nhóm phát triển sử dụng. - Sử dụng các khóa cache hợp lý: Bao gồm các tệp khóa trong khóa cache để giúp tăng cường hiệu suất.
Những cạm bẫy thường gặp
- Quên kiểm tra các ràng buộc phiên bản: Đảm bảo rằng các ràng buộc phiên bản trong tệp cấu hình và tệp khóa là nhất quán.
- Không sử dụng lệnh lock: Việc không sử dụng
terraform providers lockcó thể dẫn đến việc thiếu các hash cần thiết cho các nền tảng khác nhau.
Mẹo hiệu suất
- Tối ưu hóa cấu hình pipeline: Nên xem xét cấu hình pipeline để giảm thiểu thời gian cài đặt và khởi tạo.
- Sử dụng caching hiệu quả: Thiết lập caching cho thư mục
.terraformgiúp tiết kiệm thời gian đáng kể trong các lần chạy tiếp theo.
Khắc phục sự cố
- Nếu cache không hoạt động: Kiểm tra quyền truy cập và cấu hình của cache, cũng như đảm bảo rằng các tệp khóa được cập nhật chính xác.
Kết luận
Kinh nghiệm này không mang lại cho tôi cảm giác thoải mái với cách triển khai hiện tại. Có vẻ như nó sẽ luôn tải xuống nhị phân từ registry để thêm checksum. Sẽ hiệu quả hơn nhiều nếu lưu trữ các checksum này dưới dạng siêu dữ liệu trong registry. Điều này có thể yêu cầu thay đổi cả backend và frontend. Một lựa chọn thay thế sẽ là ít nhất tính toán các checksum dựa trên các phiên bản có sẵn tại chỗ của provider. Điều này có thể giúp giảm tải cho CDN của Hashicorp.
FAQ
Tại sao Terraform không sử dụng các provider đã cache?
Terraform có thể không nhận ra các hash trong tệp .terraform.lock.hcl. Đảm bảo rằng tệp này được cập nhật với các phiên bản và hash chính xác cho các nền tảng khác nhau.
Làm thế nào để tối ưu hóa pipeline CI/CD với Terraform?
Sử dụng caching cho thư mục .terraform và thường xuyên kiểm tra tệp .terraform.lock.hcl để đảm bảo tính nhất quán về phiên bản.
Cần lưu ý điều gì khi sử dụng Terraform trên nhiều nền tảng?
Hãy chắc chắn rằng các hash cho tất cả các nền tảng mà nhóm phát triển sử dụng đều có mặt trong tệp khóa phụ thuộc.