ENECHANGE Developer Blog

ENECHANGE開発者ブログ

ALBで複数のWebサーバをコントロールする

こんにちは。先日、富士の麓でソロキャンで思う存分焚き火してきたCTO室のkazです。 今日は、フロントエンド開発者が使用しているサーバのサーバ証明書 の有効期間が過ぎてしまっており、「更新よろ」と言われていたタスクを隙間時間で対応した記事を書きたいと思います。

これまで、開発者用サーバのサーバ証明書は、サーバ毎に配置し更新時などは管理工数が大きくなりがちでした。この機会にAWSのACMでサーバ証明書を管理できるように構成を変更し、その過程で、ALBのホストベースによるルーティングを使用してホストヘッダ毎にリクエストを割り振るように再設計しました。 個人的にはECSで開発してほしいんですが、開発者側で事情がいろいろあるようなので、今後のタスクとして積んでおきます...!

ELBには古くからあるCLBの他に、2016年に発表されたALB、2017年に発表されたNLBがあります。この中で、サーバ証明書を利用できるのはCLBかALBになります。

ALB・NLB・CLBの比較はこちらにありますが、ルーティングにおいては、ALBはホストベースの他、コンテンツベース、パスベースにも対応しており、高レイヤーでのルーテイングが可能です。

今回は、要件として、各ホスト毎に接続するEC2が異なる必要がありました。例えば、下図において、yell.enechange.jpにアクセスが来た場合は、yellというEC2に接続される必要がありました。 CLBを用いると、ホストベースによるルーテイングができないため、各EC2毎にCLBをつける必要があり、開発者が増える毎にCLBが増え、新たなElasticIPも必要になってきます。ALBのホストベースによるルーティングを使用することで、ElasticIPとCLBを節約できました。

また、当初の目的であるサーバ証明書のACMでの管理も達成でき、運用コストも下げることができました。

Befor After

f:id:dev-enechange:20180822104941p:plain

設定ファイル構成

ENECHANGEでは、以前の記事(TerraformでVPC周りを作成する)で紹介した通り、AWSの設計はTerraformを用いて行なっています。今回もTerraformの設定をご紹介します!

elb
└── application
    ├── lb.tf
    ├── main.tf
    ├── security_group.tf
    ├── terraform_remote_state.tf
    └── variabls.tf

variables.tf

  • 同一ALBに複数のリスナーがある場合は同じ優先順位を持つ複数のルールを持つことはできないので、明示的に定義する必要があります
variable "VPCId" {
  default = "vpc-xxxxxx"
}

variable "priorityies" {
  type = "list"

  default = [
    "100",
    "101",
    "102",
    "103",
    "104",
    "105",
    "106",
  ]
}

variable "host_headers" {
  type = "list"

  default = [
    "yell.enechange.com",
    "ange.enechange.com",
    "amor.enechange.com",
    "macherie.enechange.com",
    "etoile.enechange.com",
    "whip.enechange.com",
  ]
}

variable "instance_list" {
  type = "list"

  default = [
    "i-xxxxxx", //yell
    "i-xxxxxx", //ange
    "i-xxxxxx", //amor
    "i-xxxxxx", //macherie
    "i-xxxxxx", //etoile
    "i-xxxxxx", //whip
  ]
}

variable "target_group_names" {
  type = "list"

  default = [
    "yell-enechange-alb-target",
    "ange-enechange-alb-target",
    "amor-enechange-alb-target",
    "macherie-enechange-alb-target",
    "etoile-enechange-alb-target",
    "whip-enechange-alb-target",
  ]
}

security_group.tf

resource "aws_security_group" "dev_enechange_alb_sg" {
  vpc_id = "${var.VPCId}"
  name   = "dev_enechange_alb_sg"

  ingress {
    from_port   = "443"
    to_port     = "443"
    protocol    = "tcp"
    cidr_blocks = ["***.***.***.***/32"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags {
    Name        = "dev-enechange-alb"
    Service     = "enechange"
    Environment = "development"
    Description = "Managed by Terraform"
  }
}

lb.tf

resource "aws_lb" "dev_enechange_alb" {
  name               = "dev-enechange-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = ["${aws_security_group.dev_enechange_alb_sg.id}"]
  subnets            = ["subnet-*****", "subnet-*****"]

  enable_deletion_protection = true

  access_logs {
    bucket  = "enechange-logs"
    prefix  = "dev-enechange/alb"
    enabled = true
  }

  tags {
    Name        = "dev-enechange-alb"
    Environment = "development"
    Service     = "enechange"
    Description = "Managed by Terraform"
  }
}

resource "aws_lb_target_group" "dev_enechange_alb_target" {
  count    = "${length(var.instance_list)}"
  name     = "${element(var.target_group_names, count.index)}"
  port     = 443
  protocol = "HTTPS"
  vpc_id   = "${var.VPCId}"

  health_check {
    interval            = 30
    path                = "/try/ping"
    port                = 443
    protocol            = "HTTPS"
    timeout             = 5
    unhealthy_threshold = 2
    matcher             = 200
  }
}

resource "aws_lb_target_group_attachment" "dev_enechange_alb_target_group_attachment" {
  count            = "${length(var.instance_list)}"
  target_group_arn = "${element(aws_lb_target_group.dev_enechange_alb_target.*.arn, count.index)}"
  target_id        = "${element(var.instance_list, count.index)}"
  port             = 443
}

resource "aws_lb_listener" "dev_enechange_alb_listner" {
  load_balancer_arn = "${aws_lb.dev_enechange_alb.arn}"
  port              = "443"
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-2016-08"
  certificate_arn   = "arn:aws:acm:ap-northeast-1:*****:*****/***********************************"

  default_action {
    target_group_arn = "${element(aws_lb_target_group.dev_enechange_alb_target.*.arn, count.index)}"
    type             = "forward"
  }
}

resource "aws_lb_listener_rule" "dev_enechange_alb_listner_rule" {
  count        = "${length(var.instance_list)}"
  listener_arn = "${element(aws_lb_listener.dev_enechange_alb_listner.*.arn, count.index)}"
  priority     = "${element(var.priorityies, count.index + 1)}"

  action {
    type             = "forward"
    target_group_arn = "${element(aws_lb_target_group.dev_enechange_alb_target.*.arn, count.index)}"
  }

  condition {
    field  = "host-header"
    values = ["${element(var.host_headers, count.index)}"]
  }
}

確認

  • TargetGroup
$ aws elbv2 describe-target-groups --load-balancer-arn arn:aws:elasticloadbalancing:ap-northeast-1:************:loadbalancer/app/dev-enechange-alb/:**************** --query 'TargetGroups[].TargetGroupName[]'
[
    "yell-enechange-alb-target",
    "ange-enechange-alb-target",
    "amor-enechange-alb-target",
    "macherie-enechange-alb-target",
    "etoile-enechange-alb-target",
    "whip-enechange-alb-target"
]
  • Rules
$ aws elbv2 describe-rules --rule-arns \
> "arn:aws:elasticloadbalancing:ap-northeast-1:************:listener-rule/app/dev-enechange-alb/****************/****************/****************" \
> "arn:aws:elasticloadbalancing:ap-northeast-1:************:listener-rule/app/dev-enechange-alb/****************/****************/****************" \
> "arn:aws:elasticloadbalancing:ap-northeast-1:************:listener-rule/app/dev-enechange-alb/****************/****************/****************" \
> "arn:aws:elasticloadbalancing:ap-northeast-1:************:listener-rule/app/dev-enechange-alb/****************/****************/****************" \
> "arn:aws:elasticloadbalancing:ap-northeast-1:************:listener-rule/app/dev-enechange-alb/****************/****************/****************" \
> "arn:aws:elasticloadbalancing:ap-northeast-1:************:listener-rule/app/dev-enechange-alb/****************/****************/****************" \
> "arn:aws:elasticloadbalancing:ap-northeast-1:************:listener-rule/app/dev-enechange-alb/****************/****************/****************" \
> "arn:aws:elasticloadbalancing:ap-northeast-1:************:listener-rule/app/dev-enechange-alb/****************/****************/****************" \
> --query 'Rules[].Conditions[].Values[]'
[
    "yell.enechange.com",
    "ange.enechange.com",
    "amor.enechange.com",
    "macherie.enechange.com",
    "etoile.enechange.com",
    "whip.enechange.com"
]
  • これで、運用コストが激減したはず...!