Cạm bẫy địa hình

Cạm bẫy địa hình
Hãy nêu bật một số cạm bẫy, bao gồm những cạm bẫy liên quan đến vòng lặp, câu lệnh if và kỹ thuật triển khai, cũng như các vấn đề chung hơn ảnh hưởng đến Terraform nói chung:

  • các tham số count và for_each có những hạn chế;
  • hạn chế triển khai thời gian ngừng hoạt động bằng không;
  • ngay cả một kế hoạch tốt cũng có thể thất bại;
  • tái cấu trúc có thể có những cạm bẫy;
  • sự kết hợp trì hoãn là nhất quán... với sự trì hoãn.

Các tham số count và for_each có giới hạn

Các ví dụ trong chương này sử dụng rộng rãi tham số count và biểu thức for_each trong vòng lặp và logic điều kiện. Chúng hoạt động tốt nhưng có hai hạn chế quan trọng mà bạn cần lưu ý.

  • Count và for_each không thể tham chiếu bất kỳ biến đầu ra tài nguyên nào.
  • count và for_each không thể được sử dụng trong cấu hình mô-đun.

count và for_each không thể tham chiếu bất kỳ biến đầu ra tài nguyên nào

Hãy tưởng tượng bạn cần triển khai một số máy chủ EC2 và vì lý do nào đó bạn không muốn sử dụng ASG. Mã của bạn có thể như thế này:

resource "aws_instance" "example_1" {
   count             = 3
   ami                = "ami-0c55b159cbfafe1f0"
   instance_type = "t2.micro"
}

Chúng ta hãy nhìn vào chúng từng cái một.

Vì tham số count được đặt thành giá trị tĩnh nên mã này sẽ hoạt động mà không gặp vấn đề gì: khi bạn chạy lệnh áp dụng, nó sẽ tạo ra ba máy chủ EC2. Nhưng điều gì sẽ xảy ra nếu bạn muốn triển khai một máy chủ ở mỗi Vùng sẵn sàng (AZ) trong khu vực AWS hiện tại của mình? Bạn có thể yêu cầu mã của mình tải danh sách các vùng từ nguồn dữ liệu aws_availability_zones, sau đó lặp qua từng vùng và tạo máy chủ EC2 trong đó bằng cách sử dụng tham số count và truy cập chỉ mục mảng:

resource "aws_instance" "example_2" {
   count                   = length(data.aws_availability_zones.all.names)
   availability_zone   = data.aws_availability_zones.all.names[count.index]
   ami                     = "ami-0c55b159cbfafe1f0"
   instance_type       = "t2.micro"
}

data "aws_availability_zones" "all" {}

Mã này cũng sẽ hoạt động tốt vì tham số count có thể tham chiếu các nguồn dữ liệu mà không gặp bất kỳ sự cố nào. Nhưng điều gì sẽ xảy ra nếu số lượng máy chủ bạn cần tạo phụ thuộc vào đầu ra của một số tài nguyên? Để chứng minh điều này, cách dễ nhất là sử dụng tài nguyên Random_integer, như tên cho thấy, tài nguyên này trả về một số nguyên ngẫu nhiên:

resource "random_integer" "num_instances" {
  min = 1
  max = 3
}

Mã này tạo ra một số ngẫu nhiên từ 1 đến 3. Hãy xem điều gì sẽ xảy ra nếu chúng ta cố gắng sử dụng đầu ra của tài nguyên này trong tham số count của tài nguyên aws_instance:

resource "aws_instance" "example_3" {
   count             = random_integer.num_instances.result
   ami                = "ami-0c55b159cbfafe1f0"
   instance_type = "t2.micro"
}

Nếu bạn chạy terraform plan trên mã này, bạn sẽ gặp lỗi sau:

Error: Invalid count argument

   on main.tf line 30, in resource "aws_instance" "example_3":
   30: count = random_integer.num_instances.result

The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the count depends on.

Terraform yêu cầu tính toán count và for_each trong giai đoạn lập kế hoạch, trước khi bất kỳ tài nguyên nào được tạo hoặc sửa đổi. Điều này có nghĩa là count và for_each có thể tham chiếu đến hằng số, biến, nguồn dữ liệu và thậm chí cả danh sách tài nguyên (miễn là độ dài của chúng có thể được xác định tại thời điểm lên lịch), nhưng không tham chiếu đến các biến đầu ra tài nguyên được tính toán.

count và for_each không thể được sử dụng trong cấu hình mô-đun

Một ngày nào đó bạn có thể muốn thêm tham số count vào cấu hình mô-đun của mình:

module "count_example" {
     source = "../../../../modules/services/webserver-cluster"

     count = 3

     cluster_name = "terraform-up-and-running-example"
     server_port = 8080
     instance_type = "t2.micro"
}

Mã này cố gắng sử dụng số lượng bên trong mô-đun để tạo ba bản sao của tài nguyên cụm máy chủ web. Hoặc bạn có thể muốn biến việc kết nối một mô-đun thành tùy chọn dựa trên một số điều kiện Boolean bằng cách đặt tham số count của nó thành 0. Mã này có thể trông giống như mã hợp lý, nhưng bạn sẽ gặp lỗi này khi chạy terraform plan:

Error: Reserved argument name in module block

   on main.tf line 13, in module "count_example":
   13: count = 3

The name "count" is reserved for use in a future version of Terraform.

Thật không may, kể từ Terraform 0.12.6, việc sử dụng count hoặc for_each trong tài nguyên mô-đun không được hỗ trợ. Theo ghi chú phát hành Terraform 0.12 (http://bit.ly/3257bv4), HashiCorp có kế hoạch bổ sung khả năng này trong tương lai, do đó, tùy thuộc vào thời điểm bạn đọc cuốn sách này, nó có thể đã có sẵn. Để tìm hiểu chắc chắn, đọc nhật ký thay đổi Terraform tại đây.

Hạn chế của việc triển khai không có thời gian ngừng hoạt động

Sử dụng khối create_b Before_destroy kết hợp với ASG là một giải pháp tuyệt vời để tạo ra các hoạt động triển khai không có thời gian ngừng hoạt động, ngoại trừ một lưu ý: quy tắc tự động điều chỉnh quy mô không được hỗ trợ. Hay nói chính xác hơn, thao tác này sẽ đặt lại kích thước ASG về min_size trong mỗi lần triển khai. Đây có thể là một vấn đề nếu bạn đang sử dụng quy tắc tự động điều chỉnh quy mô để tăng số lượng máy chủ đang chạy.

Ví dụ: mô-đun cụm máy chủ web chứa một cặp tài nguyên aws_autoscaling_schedule, vào lúc 9 giờ sáng sẽ tăng số lượng máy chủ trong cụm từ hai lên mười. Nếu bạn triển khai vào lúc 11 giờ sáng, ASG mới sẽ khởi động chỉ với hai máy chủ thay vì mười và giữ nguyên như vậy cho đến 9 giờ sáng ngày hôm sau.

Hạn chế này có thể được khắc phục bằng nhiều cách.

  • Thay đổi tham số lặp lại trong aws_autoscaling_schedule từ 0 9 * * * (“chạy lúc 9 giờ sáng”) thành giá trị như 0-59 9-17 * * * (“chạy mỗi phút từ 9 giờ sáng đến 5 giờ chiều”). Nếu ASG đã có mười máy chủ, việc chạy lại quy tắc tự động điều chỉnh tỷ lệ này sẽ không thay đổi bất cứ điều gì, đó là điều chúng tôi muốn. Nhưng nếu ASG chỉ mới được triển khai gần đây, quy tắc này sẽ đảm bảo rằng trong tối đa một phút, số lượng máy chủ của nó sẽ lên tới mười. Đây không phải là một cách tiếp cận hoàn toàn tao nhã và việc nhảy vọt từ mười lên hai máy chủ rồi quay lại cũng có thể gây ra sự cố cho người dùng.
  • Tạo tập lệnh tùy chỉnh sử dụng API AWS để xác định số lượng máy chủ đang hoạt động trong ASG, gọi nó bằng nguồn dữ liệu bên ngoài (xem "Nguồn dữ liệu bên ngoài" trên trang 249) và đặt tham số mong muốn_capacity của ASG thành giá trị được trả về bởi kịch bản. Bằng cách này, mỗi phiên bản ASG mới sẽ luôn chạy ở cùng công suất với mã Terraform hiện có và khiến việc bảo trì trở nên khó khăn hơn.

Tất nhiên, lý tưởng nhất là Terraform sẽ có hỗ trợ tích hợp để triển khai không có thời gian ngừng hoạt động, nhưng kể từ tháng 2019 năm XNUMX, nhóm HashiCorp không có kế hoạch bổ sung chức năng này (chi tiết - tại đây).

Kế hoạch đúng có thể không được thực hiện không thành công

Đôi khi lệnh kế hoạch tạo ra một kế hoạch triển khai hoàn toàn chính xác, nhưng lệnh áp dụng lại trả về lỗi. Ví dụ: hãy thử thêm tài nguyên aws_iam_user có cùng tên mà bạn đã sử dụng cho người dùng IAM mà bạn đã tạo trước đó trong Chương 2:

resource "aws_iam_user" "existing_user" {
   # Подставьте сюда имя уже существующего пользователя IAM,
   # чтобы попрактиковаться в использовании команды terraform import
   name = "yevgeniy.brikman"
}

Bây giờ, nếu bạn chạy lệnh plan, Terraform sẽ đưa ra một kế hoạch triển khai có vẻ hợp lý:

Terraform will perform the following actions:

   # aws_iam_user.existing_user will be created
   + resource "aws_iam_user" "existing_user" {
         + arn                  = (known after apply)
         + force_destroy   = false
         + id                    = (known after apply)
         + name               = "yevgeniy.brikman"
         + path                 = "/"
         + unique_id         = (known after apply)
      }

Plan: 1 to add, 0 to change, 0 to destroy.

Nếu bạn chạy lệnh áp dụng, bạn sẽ gặp lỗi sau:

Error: Error creating IAM User yevgeniy.brikman: EntityAlreadyExists:
User with name yevgeniy.brikman already exists.

   on main.tf line 10, in resource "aws_iam_user" "existing_user":
   10: resource "aws_iam_user" "existing_user" {

Tất nhiên, vấn đề là người dùng IAM có tên đó đã tồn tại. Và điều này có thể xảy ra không chỉ với người dùng IAM mà còn với hầu hết mọi tài nguyên. Có thể ai đó đã tạo tài nguyên này theo cách thủ công hoặc sử dụng dòng lệnh, nhưng dù thế nào đi nữa, việc trùng khớp ID sẽ dẫn đến xung đột. Có rất nhiều biến thể của lỗi này thường khiến những người mới sử dụng Terraform phải ngạc nhiên.

Điểm mấu chốt là lệnh terraform plan chỉ tính đến những tài nguyên được chỉ định trong tệp trạng thái Terraform. Nếu tài nguyên được tạo theo cách khác (ví dụ: thủ công bằng cách nhấp vào bảng điều khiển AWS), chúng sẽ không xuất hiện trong tệp trạng thái và do đó Terraform sẽ không tính đến chúng khi thực thi lệnh kế hoạch. Kết quả là, một kế hoạch thoạt nhìn có vẻ đúng đắn sẽ không thành công.

Có hai bài học được rút ra từ điều này.

  • Nếu bạn đã bắt đầu làm việc với Terraform, đừng sử dụng bất cứ thứ gì khác. Nếu một phần cơ sở hạ tầng của bạn được quản lý bằng Terraform, bạn không thể sửa đổi nó theo cách thủ công nữa. Nếu không, bạn không chỉ gặp rủi ro với các lỗi Terraform kỳ lạ mà còn phủ nhận nhiều lợi ích của IaC vì mã sẽ không còn thể hiện chính xác cơ sở hạ tầng của bạn nữa.
  • Nếu bạn đã có một số cơ sở hạ tầng, hãy sử dụng lệnh nhập. Nếu bạn đang bắt đầu sử dụng Terraform với cơ sở hạ tầng hiện có, bạn có thể thêm nó vào tệp trạng thái bằng lệnh nhập terraform. Bằng cách này, Terraform sẽ biết cơ sở hạ tầng nào cần được quản lý. Lệnh nhập có hai đối số. Đầu tiên là địa chỉ tài nguyên trong tệp cấu hình của bạn. Cú pháp ở đây giống như đối với các liên kết tài nguyên: _. (như aws_iam_user.ex hiện_user). Đối số thứ hai là ID của tài nguyên được nhập. Giả sử ID tài nguyên aws_iam_user là tên người dùng (ví dụ: yevgeniy.brikman) và ID tài nguyên aws_instance là ID máy chủ EC2 (như i-190e22e5). Cách nhập tài nguyên thường được chỉ ra trong tài liệu ở cuối trang.

    Dưới đây là lệnh nhập đồng bộ hóa tài nguyên aws_iam_user mà bạn đã thêm vào cấu hình Terraform của mình cùng với người dùng IAM trong Chương 2 (tất nhiên thay thế tên của bạn cho yevgeniy.brikman):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    Terraform sẽ gọi API AWS để tìm người dùng IAM của bạn và tạo liên kết tệp trạng thái giữa người dùng đó và tài nguyên aws_iam_user.being_user trong cấu hình Terraform của bạn. Từ giờ trở đi, khi bạn chạy lệnh kế hoạch, Terraform sẽ biết rằng người dùng IAM đã tồn tại và sẽ không cố gắng tạo lại.

    Điều đáng lưu ý là nếu bạn đã có nhiều tài nguyên muốn nhập vào Terraform, việc viết mã theo cách thủ công và nhập từng tài nguyên một lần có thể gặp rắc rối. Vì vậy, bạn nên xem xét một công cụ như Terraforming (http://terraforming.dtan4.net/), công cụ này có thể tự động nhập mã và trạng thái từ tài khoản AWS của bạn.

    Tái cấu trúc có thể có những cạm bẫy

    Tái cấu trúc là một cách phổ biến trong lập trình trong đó bạn thay đổi cấu trúc bên trong của mã trong khi vẫn giữ nguyên hành vi bên ngoài. Điều này nhằm làm cho mã rõ ràng hơn, gọn gàng hơn và dễ bảo trì hơn. Tái cấu trúc là một kỹ thuật không thể thiếu và nên được sử dụng thường xuyên. Nhưng khi nói đến Terraform hoặc bất kỳ công cụ IaC nào khác, bạn phải cực kỳ cẩn thận về ý nghĩa của “hành vi bên ngoài” của một đoạn mã, nếu không sẽ phát sinh những vấn đề không mong muốn.

    Ví dụ, một kiểu tái cấu trúc phổ biến là thay thế tên của các biến hoặc hàm bằng những tên dễ hiểu hơn. Nhiều IDE có hỗ trợ tái cấu trúc tích hợp và có thể tự động đổi tên các biến và hàm trong suốt dự án. Trong các ngôn ngữ lập trình đa năng, đây là một quy trình tầm thường mà bạn có thể không nghĩ tới, nhưng trong Terraform, bạn phải cực kỳ cẩn thận với điều này, nếu không bạn có thể gặp phải tình trạng ngừng hoạt động.

    Ví dụ: mô-đun cụm máy chủ web có biến đầu vào cluster_name:

    variable "cluster_name" {
       description = "The name to use for all the cluster resources"
       type          = string
    }

    Hãy tưởng tượng rằng bạn bắt đầu sử dụng mô-đun này để triển khai một microservice có tên foo. Sau này, bạn muốn đổi tên dịch vụ của mình thành bar. Sự thay đổi này nghe có vẻ tầm thường nhưng trên thực tế nó có thể gây gián đoạn dịch vụ.

    Thực tế là mô-đun cụm máy chủ web sử dụng biến cluster_name trong một số tài nguyên, bao gồm tham số tên của hai nhóm bảo mật và ALB:

    resource "aws_lb" "example" {
       name                    = var.cluster_name
       load_balancer_type = "application"
       subnets = data.aws_subnet_ids.default.ids
       security_groups      = [aws_security_group.alb.id]
    }

    Nếu bạn thay đổi tham số tên trên một tài nguyên, Terraform sẽ xóa phiên bản cũ của tài nguyên đó và tạo một tài nguyên mới thay thế. Nhưng nếu tài nguyên đó là ALB, giữa việc xóa nó và tải xuống phiên bản mới, bạn sẽ không có cơ chế chuyển hướng lưu lượng truy cập đến máy chủ web của mình. Tương tự, nếu một nhóm bảo mật bị xóa, máy chủ của bạn sẽ bắt đầu từ chối mọi lưu lượng truy cập mạng cho đến khi một nhóm mới được tạo.

    Một kiểu tái cấu trúc khác mà bạn có thể quan tâm là thay đổi ID Terraform. Hãy lấy tài nguyên aws_security_group trong mô-đun cụm máy chủ web làm ví dụ:

    resource "aws_security_group" "instance" {
      # (...)
    }

    Mã định danh của tài nguyên này được gọi là instance. Hãy tưởng tượng rằng trong quá trình tái cấu trúc, bạn quyết định thay đổi nó thành một tên cluster_instance dễ hiểu hơn (theo ý kiến ​​của bạn):

    resource "aws_security_group" "cluster_instance" {
       # (...)
    }

    Điều gì sẽ xảy ra cuối cùng? Đúng vậy: một sự gián đoạn.

    Terraform liên kết từng ID tài nguyên với ID nhà cung cấp đám mây. Ví dụ: iam_user được liên kết với ID người dùng AWS IAM và aws_instance được liên kết với ID máy chủ AWS EC2. Nếu bạn thay đổi ID tài nguyên (giả sử từ phiên bản thành cluster_instance, như trường hợp của aws_security_group), thì thành Terraform, nó sẽ xuất hiện như thể bạn đã xóa tài nguyên cũ và thêm tài nguyên mới. Nếu bạn áp dụng những thay đổi này, Terraform sẽ xóa nhóm bảo mật cũ và tạo nhóm bảo mật mới, trong khi máy chủ của bạn bắt đầu từ chối mọi lưu lượng truy cập mạng.

    Dưới đây là bốn bài học quan trọng bạn nên rút ra từ cuộc thảo luận này.

    • Luôn sử dụng lệnh kế hoạch. Nó có thể tiết lộ tất cả những nhược điểm này. Xem xét đầu ra của nó một cách cẩn thận và chú ý đến các tình huống mà Terraform có kế hoạch xóa các tài nguyên mà rất có thể không nên xóa.
    • Tạo trước khi bạn xóa. Nếu bạn muốn thay thế một tài nguyên, hãy suy nghĩ cẩn thận xem bạn có cần tạo tài nguyên thay thế trước khi xóa bản gốc hay không. Nếu câu trả lời là có, create_b Before_destroy có thể giúp ích. Có thể đạt được kết quả tương tự theo cách thủ công bằng cách thực hiện hai bước: đầu tiên thêm tài nguyên mới vào cấu hình và chạy lệnh áp dụng, sau đó xóa tài nguyên cũ khỏi cấu hình và sử dụng lại lệnh áp dụng.
    • Thay đổi số nhận dạng yêu cầu thay đổi trạng thái. Nếu bạn muốn thay đổi ID được liên kết với tài nguyên (ví dụ: đổi tên aws_security_group từ phiên bản thành cluster_instance) mà không xóa tài nguyên và tạo phiên bản mới của tài nguyên đó, thì bạn phải cập nhật tệp trạng thái Terraform tương ứng. Đừng bao giờ thực hiện việc này một cách thủ công - thay vào đó hãy sử dụng lệnh terraform state. Khi đổi tên số nhận dạng, bạn nên chạy lệnh mv trạng thái terraform, có cú pháp sau:
      terraform state mv <ORIGINAL_REFERENCE> <NEW_REFERENCE>

      ORIGINAL_REFERENCE là biểu thức đề cập đến tài nguyên ở dạng hiện tại và NEW_REFERENCE là nơi bạn muốn di chuyển tài nguyên đó. Ví dụ: khi đổi tên nhóm aws_security_group từ phiên bản thành cluster_instance, bạn cần chạy lệnh sau:

      $ terraform state mv 
         aws_security_group.instance 
         aws_security_group.cluster_instance

      Điều này cho Terraform biết rằng trạng thái trước đây được liên kết với aws_security_group.instance giờ đây sẽ được liên kết với aws_security_group.cluster_instance. Nếu sau khi đổi tên và chạy lệnh này, sơ đồ địa hình không hiển thị bất kỳ thay đổi nào thì bạn đã làm mọi thứ chính xác.

    • Một số cài đặt không thể thay đổi. Các thông số của nhiều tài nguyên là không thể thay đổi. Nếu bạn cố gắng thay đổi chúng, Terraform sẽ xóa tài nguyên cũ và tạo một tài nguyên mới vào vị trí của nó. Mỗi trang tài nguyên thường sẽ cho biết điều gì sẽ xảy ra khi bạn thay đổi một cài đặt cụ thể, vì vậy hãy nhớ kiểm tra tài liệu. Luôn sử dụng lệnh kế hoạch và cân nhắc sử dụng chiến lược create_Before_destroy.

    Tính nhất quán bị trì hoãn là nhất quán... với sự trì hoãn

    API của một số nhà cung cấp đám mây, chẳng hạn như AWS, không đồng bộ và có độ nhất quán chậm. Không đồng bộ có nghĩa là giao diện có thể trả về phản hồi ngay lập tức mà không cần đợi hành động được yêu cầu hoàn tất. Tính nhất quán bị trì hoãn có nghĩa là những thay đổi có thể mất thời gian để lan truyền khắp hệ thống; trong khi điều này đang xảy ra, phản hồi của bạn có thể không nhất quán và phụ thuộc vào bản sao nguồn dữ liệu nào đang phản hồi lệnh gọi API của bạn.

    Ví dụ: hãy tưởng tượng bạn thực hiện lệnh gọi API tới AWS để yêu cầu nó tạo máy chủ EC2. API sẽ trả về phản hồi “thành công” (201 Created) gần như ngay lập tức mà không cần đợi máy chủ được tạo. Nếu bạn cố gắng kết nối ngay thì gần như chắc chắn sẽ thất bại vì lúc đó AWS vẫn đang khởi tạo tài nguyên hoặc nói cách khác là máy chủ chưa khởi động. Hơn nữa, nếu bạn thực hiện một cuộc gọi khác để lấy thông tin về máy chủ này, bạn có thể gặp lỗi (404 Not Found). Vấn đề là thông tin về máy chủ EC2 này có thể vẫn được truyền bá khắp AWS trước khi nó có mặt ở khắp mọi nơi, bạn sẽ phải đợi vài giây.

    Bất cứ khi nào bạn sử dụng API không đồng bộ với tính nhất quán lười biếng, bạn phải định kỳ thử lại yêu cầu của mình cho đến khi hành động hoàn tất và lan truyền trong hệ thống. Thật không may, AWS SDK không cung cấp bất kỳ công cụ tốt nào cho việc này và dự án Terraform từng gặp rất nhiều lỗi như 6813 (https://github.com/hashicorp/terraform/issues/6813):

    $ terraform apply
    aws_subnet.private-persistence.2: InvalidSubnetID.NotFound:
    The subnet ID 'subnet-xxxxxxx' does not exist

    Nói cách khác, bạn tạo một tài nguyên (như mạng con) và sau đó cố gắng lấy một số thông tin về nó (như ID của mạng con mới được tạo) và Terraform không thể tìm thấy nó. Hầu hết các lỗi này (bao gồm cả lỗi 6813) đã được sửa nhưng thỉnh thoảng chúng vẫn xuất hiện, đặc biệt là khi Terraform bổ sung hỗ trợ cho một loại tài nguyên mới. Điều này gây khó chịu nhưng trong hầu hết các trường hợp không gây ra bất kỳ tác hại nào. Khi bạn chạy lại ứng dụng terraform, mọi thứ sẽ hoạt động vì đến thời điểm này thông tin đã lan truyền khắp hệ thống.

    Đoạn trích này được trình bày từ cuốn sách của Evgeniy Brikman "Terraform: cơ sở hạ tầng ở cấp độ mã".

Nguồn: www.habr.com

Thêm một lời nhận xét