#はじめに
皆さん、業務で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のみでやってきたので、あまり格好いいコードはかけていないと思います。(ぶっちゃけ晒したくない。怖い)
なので、そういった観点からもコメントいただければありがたく思います!
以上、お疲れ様でした。