TerraformでAWS Elasticsearchを自動構築をやってみた(with Cognito認証ON)

Terraform

最近はやっているTerraformを利用して、AWS Elasticsearchを自動構築してみたので、その内容をまとめたいと思います。

ややこしいことはいいから、ソースコードよこせって方はこちらになります。

GitHub - kackey0-1/spring-logstash
Contribute to kackey0-1/spring-logstash development by creating an account on GitHub.

Kibanaを外部に公開するためにCognito認証を有効にして、Kibanaをインターネット経由でアクセスできるように構築しました。。

Elasticsearchへのindex登録についてはPrivate Subnet経由のみ可能になっています。

インフラ構成

各種AWSリソースの構成は、以下のようになります。

VPC

VPCはアプリケーションリソースを配置するネットワーク空間であり、Network Rangeの指定によって配置できるインスタンスの数が制限されます。

今回は、"10.0.0.0/16"にてVPCを設定します。

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  instance_tenancy     = "default"
  enable_dns_support   = "true"
  enable_dns_hostnames = "true"
  enable_classiclink   = "false"

  tags = merge(
  var.DEFAULT_TAGS,
  map("Name", lower("${var.PREFIX}-${var.ENV}-VPC"))
  )
}

Subnet

外部からアクセスを制御するネットワーク帯を分けるため、SubnetをPrivate/Publicに作成します。

Public Subnet内のインスタンスにはインターネット経由でアクセスをすることができます。

また、本来はマルチAZまたがるSubnetを作成し、各種インスタンスを冗長化させるのが一般的ですが、今回はシングルインスタンスでの構築のためAZは"a"のみを利用しています。

// public subnet
resource "aws_subnet" "nginx_public_0" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = var.AZ[0]
  map_public_ip_on_launch = true

  tags = merge(
  var.DEFAULT_TAGS,
  map("Name", lower("${var.PREFIX}-${var.ENV}-PUBLIC-0-SN"))
  )
}

// private subnet
resource "aws_subnet" "es_private_0" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.2.0/24"
  availability_zone       = var.AZ[0]

  tags = merge(
  var.DEFAULT_TAGS,
  map("Name", lower("${var.PREFIX}-${var.ENV}-PRIVATE-0-SN"))
  )
}

Security Group

Security Groupを作成し、「IP/SG単位でのアクセスコントロール」「Protocol/TCP Port単位でのアクセスコントロール」を行います。

項目
elasticsearch security groupInbound
443 from nginx & spring security group
443 from nginx & spring security group
nginx security groupInbound
22 from 0.0.0.0/0
443 from 0.0.0.0/0
spring security groupInbound
22 from 0.0.0.0/0
resource "aws_security_group" "nginx_sg" {
  name        = "${var.PREFIX}-${var.ENV}-NGINX-SG"
  description = "Nginx security group"
  vpc_id      = var.VPC_ID
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = merge(
  var.DEFAULT_TAGS,
  map("Name", lower("${var.PREFIX}-${var.ENV}-NGINX-SG"))
  )
}

resource "aws_security_group" "spring_sg" {
  name        = "${var.PREFIX}-${var.ENV}-SPRING-SG"
  description = "Spring security group"
  vpc_id      = var.VPC_ID
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = merge(
  var.DEFAULT_TAGS,
  map("Name", lower("${var.PREFIX}-${var.ENV}-SPRING-SG"))
  )
}

resource "aws_security_group" "es_sg" {
  name        = "${var.PREFIX}-${var.ENV}-ES-SG"
  description = "Elasticsearch security group"
  vpc_id      = var.VPC_ID
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = merge(
  var.DEFAULT_TAGS,
  map("Name", lower("${var.PREFIX}-${var.ENV}-ES-SG"))
  )
}

resource "aws_security_group_rule" "nginx_sg_internet_ssh_rule" {
  type        = "ingress"
  from_port   = 22
  to_port     = 22
  protocol    = "tcp"
  security_group_id = aws_security_group.nginx_sg.id
  cidr_blocks = ["0.0.0.0/0"]
}

resource "aws_security_group_rule" "nginx_sg_internet_https_rule" {
  type        = "ingress"
  from_port   = 443
  to_port     = 443
  protocol    = "tcp"
  security_group_id = aws_security_group.nginx_sg.id
  cidr_blocks = ["0.0.0.0/0"]
}

resource "aws_security_group_rule" "spring_sg_nginx_ssh_rule" {
  type        = "ingress"
  from_port   = 22
  to_port     = 22
  protocol    = "tcp"
  security_group_id = aws_security_group.spring_sg.id
  source_security_group_id = aws_security_group.nginx_sg.id
}


resource "aws_security_group_rule" "es_sg_spring_https_rule" {
  type        = "ingress"
  from_port   = 443
  to_port     = 443
  protocol    = "tcp"
  security_group_id = aws_security_group.es_sg.id
  source_security_group_id  = aws_security_group.spring_sg.id
}

resource "aws_security_group_rule" "es_sg_nginx_https_rule" {
  type        = "ingress"
  from_port   = 443
  to_port     = 443
  protocol    = "tcp"
  security_group_id = aws_security_group.es_sg.id
  source_security_group_id  = aws_security_group.nginx_sg.id
}

Elasticsearch

SecElasticsearch/Kibanaについては、上記で設定した内容を利用した設定を進める。

Cognito認証を利用する設定をしているが、その点における設定については、Githubのソースコードをご確認ください。

resource "aws_elasticsearch_domain" "es" {
  domain_name = "${lower(var.PREFIX)}-elasticsearch"
  elasticsearch_version = "7.10"

  cluster_config {
    instance_type = var.ES_INSTANCE
  }
  ebs_options {
    ebs_enabled = true
    volume_type = "gp2"
    volume_size = var.ES_VOLUME_GB
  }

  vpc_options {
    subnet_ids = var.SUBNET_IDS
    security_group_ids = var.SECURITY_GROUPS
  }

  advanced_options = {
    "rest.action.multi.allow_explicit_index" = "true"
  }
  access_policies = data.aws_iam_policy_document.es_access_policy.json

  cognito_options {
    enabled = true
    user_pool_id = lookup(var.COGNITO_MAP, "user_pool")
    identity_pool_id = lookup(var.COGNITO_MAP, "identity_pool")
    role_arn = aws_iam_role.cognito_es_role.arn
  }
  domain_endpoint_options {
    enforce_https = true
    tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
  }
  encrypt_at_rest {
    enabled = var.ES_ENCRYPTION
  }
  snapshot_options {
    automated_snapshot_start_hour = 23
  }

  tags = merge(
  var.DEFAULT_TAGS,
  map("Name", lower("${lower(var.PREFIX)}-${lower(var.ENV)}-elastic-search"))
  )

  depends_on = [aws_iam_service_linked_role.es, aws_iam_role_policy_attachment.cognito_es_attach]
}

Logstash(EC2インスタンス)

こちらはAWSリソースの設定というより、Logstashを利用してElasticsearchにログを連携する方法を紹介する。

DockerfileにてLogstashを構築しますが、その中でLogstashのlogstash-output-amazon-esプラグインをインストールします。

FROM docker.elastic.co/logstash/logstash:7.7.0
RUN logstash-plugin install logstash-output-amazon_es

そして、Logstashのpipelineを定義を決めるわけですが、outputの中では、ROLE権限を利用するためにamazon_esのディレクティブを利用する必要があります。

利用しない場合は、403のAccess Deniedのエラーが発生してしまします。

input {
  tcp {
    mode => "server"
    host => "0.0.0.0"
    port => 5044
  }
}

filter {
  json {
    source => "message"
  }
}

output {
  amazon_es {
    hosts => "${ELASTICSEARCH_HOST}"
    index => "${LOG_INDEX_NAME}"
    region => "ap-northeast-1"
  }
}

まとめ

ここまでソースコードを利用しながら説明しましたが、実際のソースコードを利用したい場合は、こちらのソースコードを利用してみてください。

以上、TerraformでElasticsearchを構築して各種サービスの管理に役立てていただければと思います。

コメント

  1. Great content! Keep up the good work!

タイトルとURLをコピーしました