4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【AWSリソース操作自動化】制限緩和申請を行う

Last updated at Posted at 2020-04-21

#はじめに

皆さん、業務でAWSは使われているでしょうか。
AWS上でお客様ごとに環境を管理していたりすると、デフォルトの制限ではリソースが足りないということがよくあると思います。
そういう場合、今まではサポートに直接文章で連絡していたと思いますが、AWS Service Quotasを使用することでCLIで制限緩和を行えるようになりました。(2019/07)
https://aws.amazon.com/jp/blogs/news/aws-service-quotas/
自動化するうえでの注意点として、試しにcliでなげてみようとするとAWS運営側に本当に申請がいってしまうため、テストする場合は無駄にコマンドを実行しないようにしましょう。

#本題
そこで、今回は事前に1環境当たりの必要リソース数とリージョン当たりの必要環境数が明らかになっている前提の元、まとめてServiceQuotasで制限緩和を提出するスクリプトを書いてみました。
このスクリプトでは、現在のquotaを確認し、必要環境数に対して不足しているかどうかを判定、不足していれば必要数まで拡張を申請します。
ついでに、再実行しても問題ないように申請ステータスも確認し、処理されていないものがあればそのリソースについては申請を行いません。

#実装
リソース事に1環境当たり必要な数を記載したaws-resource.ymlを用意しておく

quota_info:
  AutoScalingGroup:
    adjustable: true
    count: 4
    default: 200.0
    global: false
    need_increase: true
    quota_code: L-CD******
    quota_name: Auto Scaling groups per region
    service_code: autoscaling
  EIP:...

以下のジョブで制限緩和を実行

pipeline {
    options {
        buildDiscarder(logRotator(daysToKeepStr: '30', numToKeepStr: '30'))
        disableConcurrentBuilds()
    }

    parameters {
        string(name: 'NODE_LABEL', defaultValue: "sre_slave", description: '')
        string(name: 'AWS_ACCESS_KEY', description: 'Specify the access key to be used.')
        password(name: 'AWS_SECRET_ACCESS_KEY', description: 'Specify the AWS Secret Access Key to be used.')
        booleanParam(name: 'TEST_FLAG', defaultValue: true, description: 'If set to true, no Request will be submitted.')
    }

    environment {
        AWS_ACCESS_KEY_ID      = "${params.AWS_ACCESS_KEY}"
        AWS_SECRET_ACCESS_KEY  = "${params.AWS_SECRET_ACCESS_KEY}"
        AWS_DEFAULT_REGION     = "ap-northeast-1"
        AWS_DEFAULT_OUTPUT     = "json"
    }

    agent {
        docker {
            label "${NODE_LABEL}"
            image 'amazon/aws-cli'//AWSの公式cliイメージ
            args "-t --entrypoint='' --init --cap-add=SYS_ADMIN -e LANG=C.UTF-8 -e AWS_ACCESS_KEY_ID=\$AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY=\$AWS_SECRET_ACCESS_KEY -e AWS_DEFAULT_REGION='ap-northeast-1' -e AWS_DEFAULT_OUTPUT='json'"
        }
    }

    stages {
        stage('Request limit increase') {
            steps {
                script{
                    def regions = ["ap-northeast-1":110,"ap-southeast-1":15]//region_id:environment_number
                    def environment_number = 0//想定環境数
                    def current_quota      = 0//現在の制限数
                    def need_quota = 0        //必要な制限数
                    def margin     = 0        //current_quota - need_quota
                    def count_all  = 0        //複数countがあった場合の合計値
                    def region_id             //作業対象リージョン
                    def resource_name         //aws_resources.yml quota_infoのどの要素か
                    def info                  //aws_resources.yml quota_infoの要素が持つ属性の集合
                    def execute_flg           //trueなら制限緩和申請を行う
                    def current_status        //現在の制限情報の集合
                    def requested_quotas      //提出された制限緩和申請の履歴の集合
                    def update_targets_region = [:] //TEST_FLAGがtrueの場合、ここに実行コマンドを格納 resource_name:command
                    def update_targets = []  //update_targets_regionの集合
                    def resources_yaml = readYaml(file: "./aws_resources.yml")
                    def quota_infos    = resources_yaml

                    //環境が立つリージョンそれぞれに実行
                    for (region in regions.entrySet()){
                        //aws-resourceからまずservice quotaで管理されているリソースの制限緩和申請を行う
                        for (quota_info in quota_infos.quota_info.entrySet()){
                            execute_flg   = true
                            resource_name = quota_info.getKey()
                            info          = quota_info.getValue()
                            print("${resource_name}:${info.quota_name}-${info.quota_code} need_increase: ${info.need_increase}")
                            //need_increase(環境の増加によって拡張が必要)がtrueの場合のみ制限緩和対象となる
                            if (info.need_increase){
                                //グローバルサービスはリージョンが固定
                                if (info.global){
                                    print("${resource_name}:${info.service_code} is grobal service")
                                    region_id          = "us-east-1"
                                    environment_number = 100
                                } else {
                                    region_id          = region.getKey()
                                    environment_number = region.getValue()
                                }

                                current_quota = 0
                                need_quota    = 0
                                margin        = 0
                                //まず対象リソースの現状を取得
                                //get-service-quotaできないものがあるので、その場合はDefaultの値を使用する。
                                def check_result = sh(
                                    script:"export AWS_DEFAULT_REGION='${region_id}' && aws service-quotas get-service-quota --service-code ${info.service_code} --quota-code ${info.quota_code}",
                                    returnStatus: true
                                    )
                                if (check_result){//exit 0以外はtrue
                                    print("can't get current quota. so use default value.")
                                    current_quota = info.default
                                } else {
                                    //ステータスをとるか標準出力を取るかしかできないので再実行
                                    def current_status_json = sh(
                                        script:"export AWS_DEFAULT_REGION='${region_id}' && aws service-quotas get-service-quota --service-code ${info.service_code} --quota-code ${info.quota_code}",
                                        returnStdout: true
                                    )
                                    current_status = readJSON text: current_status_json
                                    current_quota  = current_status["Quota"]["Value"]
                                }

                                //必要なquotaを計算
                                need_quota = environment_number * info.count
                                margin = current_quota - need_quota
                                print("current:${current_quota} - need:${need_quota} = ${margin}")

                                //現在の値が必要なリソース数未満の場合は更新リクエストを投げる
                                if (margin >= 0){
                                    print("There is enough margin ${resource_name}:${info.service_code}")
                                    execute_flg = false
                                } else {
                                    print("need update ${resource_name}:${info.quota_name} to ${need_quota} ${region_id}")
                                    //リクエスト一覧を参照し、CASE_OPENDかになっているものがあれば更新対象からはずす
                                    //リクエストより低い値で適用されると再実行時に排除できない。
                                     def requested_quotas_json = sh (
                                        script: "export AWS_DEFAULT_REGION='${region_id}' && aws service-quotas list-requested-service-quota-change-history --service-code ${info.service_code}",
                                        returnStdout: true,
                                    )
                                    requested_quotas = readJSON text: requested_quotas_json
                                    if (requested_quotas["RequestedQuotas"].size() > 0 ){
                                        for (requested_quota in requested_quotas["RequestedQuotas"]){
                                            if (requested_quota["QuotaCode"] == "${info.quota_code}"){
                                                if (requested_quota["Status"] == "CASE_OPENED" | requested_quota["Status"] == "PENDING"){
                                                    print("The request cannot be made because there are ${requested_quota["Status"]} requests. id:${requested_quota["ServiceName"]} CaseId:${requested_quota["CaseId"]}")
                                                    execute_flg = false
                                                    break
                                                }
                                            }
                                        }
                                    }
                                }
                            } else {
                                print("No update required ${resource_name}:${info.service_code}")
                                execute_flg = false
                            }

                            //更新flgがtrueなら更新
                            if (execute_flg){
                                if (params.TEST_FLAG){//TEST_FLAGがtrueなら更新せず配列に結果を入れていく
                                    update_targets_region.put("${resource_name}", "aws service-quotas request-service-quota-increase --service-code '${info.service_code}' --quota-code '${info.quota_code}' --desired-value ${need_quota}")
                                } else {
                                    sh "export AWS_DEFAULT_REGION='${region_id}' && aws service-quotas request-service-quota-increase --service-code '${info.service_code}' --quota-code '${info.quota_code}' --desired-value ${need_quota}"
                                    print("Requested ${resource_name}:${info.quota_name} to ${need_quota} ${region_id}")
                                }
                            }
                        }
                        if (params.TEST_FLAG){
                            sh "echo '{ \'${region_id}\' : \'${update_targets_region}\' }' >${region_id}_quota.json"
                            archiveArtifacts "${region_id}_quota.json"
                        }
                    }
                }
            }
        }
    }

    post {
        always {
            cleanWs()
        }
    }
}

#おわりに
今回も、すでにあるかなと思ったらなかったので書いてみたシリーズです。
正直、私はコーディングは体系的に学んできたわけでもなく、OJTのみでやってきたので、あまり格好いいコードはかけていないと思います。(ぶっちゃけ晒したくない。怖い)
なので、そういった観点からもコメントいただければありがたく思います!

以上、お疲れ様でした。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?