Paper2 Blog

ともに、かける

Terraformはなにが難しいのか

娘(1歳半)が可愛すぎて、平日もお昼に会えるのが楽しみで仕方ないです。リモートワーク最高!!笑

さて、今日はTerraformの本質的な難しさについて考察したことを書いてみます。難しさの理解により、学習の効率化などにつながれば幸いです。

はじめに

要約

  • Terraformが難しいと感じる理由の7割以上は構築対象の仕様理解が不足していることに起因する。兎にも角にも構築対象の仕様を深く理解することが重要。

  • 要件に合わせた最適な設計をする上でサービス仕様の理解は通過点である。つまり、本来設計をしていると自然とTerraformコードが(ほぼ)書ける状態になっているはず。せっかくならIaCのメリットを享受しましょう。

前提

  • Terraformと記載していますが、ProviderとしてはAWSやAzureなどのクラウドを想定しています。
  • 当記事はAWSとAzureをTerraformで構築してきた自身の経験範囲内における考察です。
  • 主にTerraformをメインで利用していたため、Terraformにスコープを絞った書き方をしています。実際にはAWS CloudFormationやAzure Resource Managerでも難しさについては同じなのではないかと思います。

Terraformとは

概要

TerraformはHashiCorp構成言語(HCL)で記述されたコードをもとに、クラウドサービスのリソースを作成するIaCツールです。インフラをコードで記述することで、コードの再利用が可能となり複数環境の構築コスト削減や、コード管理プラクティスの適用により構成管理の高度化につながります。Terraformは長い運用を考えた際に費用対効果が優れているので活用することを推奨します。

各ツールにおけるクラウドサービスのAPIの抽象度

APIの抽象度

難しさの説明のために、まず各ツールにおけるクラウドサービスのAPIの抽象度について説明します。クラウドサービスをユーザが操作するための一番低レイヤーの方法はAPIです。例えばEKSにはリンクのようなAPIが用意されています。そのAPIでの操作を抽象化し、ライブラリとして実装したものがSDKです(EX: AWS SDK..etc)。そのSDKを利用し、CLI(EX: awscli、az...etc)やTerraformのProviderやGUI(EX: AWS Management Console、Azure Portal...etc)が作成されています。実際にはAPIの抽象度はCLI、Terraform、GUIの各実装によりますが、私の経験上以下のような傾向があると思います。

f:id:paper2parasol:20220226124529p:plain
APIの抽象度

低レイヤーから例を挙げながら比較してみましょう。

SDKCLI

まずSDKCLIですが、基本的には抽象度は同じです。APIのActionがSDKのMethodと同様CLIのコマンドにも用意されています。APIにはない追加コマンドが用意されている点で、抽象度は少し高いと整理しています。以下にEKSのAPI Action、AWSJAVA SDKのMethod、AWS CLI Commandの比較を記載しています。CLIにはkubeconfigの作成を行うupdate-kubeconfigコマンドなどが追加で用意されています。

API Action AWS JAVA SDK Method AWS CLI Command
AssociateEncryptionConfig associateEncryptionConfig associate-encryption-config
AssociateIdentityProviderConfig associateIdentityProviderConfig associate-identity-provider-config
CreateAddon createAddon create-addon
CreateCluster createCluster create-cluster
一部省略 一部省略 一部省略
UpdateNodegroupConfig updateNodegroupConfig update-nodegroup-config
UpdateNodegroupVersion updateNodegroupVersion update-nodegroup-version
- - get-token
- - update-kubeconfig
- waiter wait

SDKとTerraform

次にSDKとTerraformで比較してみます。TerraformはSDKをさらに抽象化しています。EKSの例で見てみましょう。例えばterraform applyでEKSを更新する際にはProviderで以下のコードが実行されます(全コード)。updateの中でUpdateClusterVersionやAssociateEncryptionConfigなど複数のSDKのMethodが呼び出されていることがわかります。

func resourceClusterUpdate(d *schema.ResourceData, meta interface{}) error {
    conn := meta.(*conns.AWSClient).EKSConn

    // Do any version update first.
    if d.HasChange("version") {
        input := &eks.UpdateClusterVersionInput{
            Name:    aws.String(d.Id()),
            Version: aws.String(d.Get("version").(string)),
        }

        log.Printf("[DEBUG] Updating EKS Cluster (%s) version: %s", d.Id(), input)

        // *** UpdateClusterVersionの実行 ***
        output, err := conn.UpdateClusterVersion(input)

        if err != nil {
            return fmt.Errorf("error updating EKS Cluster (%s) version: %w", d.Id(), err)
        }

        updateID := aws.StringValue(output.Update.Id)

        _, err = waitClusterUpdateSuccessful(conn, d.Id(), updateID, d.Timeout(schema.TimeoutUpdate))

        if err != nil {
            return fmt.Errorf("error waiting for EKS Cluster (%s) version update (%s): %w", d.Id(), updateID, err)
        }
    }

    if d.HasChange("encryption_config") {
        o, n := d.GetChange("encryption_config")

        if len(o.([]interface{})) == 0 && len(n.([]interface{})) == 1 {
            input := &eks.AssociateEncryptionConfigInput{
                ClusterName:      aws.String(d.Id()),
                EncryptionConfig: expandEksEncryptionConfig(d.Get("encryption_config").([]interface{})),
            }

            log.Printf("[DEBUG] Associating EKS Cluster (%s) encryption config: %s", d.Id(), input)


            // *** AssociateEncryptionConfigの実行 ***
            output, err := conn.AssociateEncryptionConfig(input)



            if err != nil {
                return fmt.Errorf("error associating EKS Cluster (%s) encryption config: %w", d.Id(), err)
            }

            updateID := aws.StringValue(output.Update.Id)

            _, err = waitClusterUpdateSuccessful(conn, d.Id(), updateID, d.Timeout(schema.TimeoutUpdate))

            if err != nil {
                return fmt.Errorf("error waiting for EKS Cluster (%s) encryption config association (%s): %w", d.Id(), updateID, err)
            }
        }
    }
// ...省略
}

CLIとTerraform

CLISDKの抽象度は基本的には同じなので、CLIとTerraformを比較した際もTerraformの方が抽象度は高いと整理しています。ただし、例外もありCLIの方が抽象度が高くなる場合があります。例えばaz ad sp create-for-rbacです。リンクの記事の通り、4工程を1回のコマンドで実施します。そのためTerraformのコードに落とす場合は、各工程分のコードを作成する必要があります。

TerraformとGUI

さて、最後にTerraformとGUIの比較です。TerraformよりGUIの方が抽象度が高い傾向にあります。GUIでリソースを作成すると、関連リソースを作成することがあります。例えばCodePipelineを作成する際は、GUIでIAMロールやCloudWatch Eventなどのリソースも同時に作成可能です。Terraformの場合は別々に作成して、連携させる必要があります。

Terraformの何が難しいと感じるのか

構築対象の仕様理解

Terraformは極端に言えば、構築対象の設定を引数として記述しているだけで、とてもシンプルなツールです。コード記述時に難しいと感じるのは、主に各引数に何を設定すればよいかわからないことや、GUIで自動作成されていた関連サービスをどのように作り、適切に連携させれば良いかがわからないことではないでしょうか。そして、わからないのはサービス仕様の理解が不足しているからだと思います。

APIの抽象度がGUIと比べTerraformの方が低いため、よりサービスの仕様を理解していないと各引数の設定値を決定できないだけでなく、GUIで同時に作成されていた関連サービスを別に作成し、適切に連携させることができないのです。各設定は多様な要件に対応するために作られたクラウドサービスの機能そのものです。それらを理解せずに最適な設計はできないと考えています。システムの要件に合わせた最適な設計をする上でサービス仕様の理解は通過点であり、本来設計をしていると自然とTerraformコードが(ほぼ)書ける状態になっていると私は考えています。逆にもしTerraformを書けない状態なのであれば、サービス仕様の理解が不足しており、最適な設計ができていない可能性があるでしょう。

当然様々な理由により、Terraformを使わないことはあるでしょう。Terraformで書いてないから最適な設計ができていないとは言いません。ただ、サービス仕様を理解し、最適な設計ができているのであればTerraformは7割くらい書けたも同然なので、せっかくならIaCのメリットを享受しましょう!とオススメをしたいわけです。

Terraform自体の難しさ

Terraformのツール自体の難しさも当然あります。いくつか例をあげますが、詳しくは当記事では説明しません。

おわりに

まとめ

Terraformが難しいと感じる理由の7割以上は構築対象の仕様理解が不足していることに起因すると思います。要件に合わせた最適な設計をする上でサービス仕様の理解は通過点であり、本来設計をしていると自然とTerraformコードが(ほぼ)書ける状態になっています。せっかくなら、TerraformでIaCしてメリットを享受しましょう。なお、本日紹介した難しさの解決策を後日記事にしようと考えています。