AWS × Nuxt.js × Go で初めて自作 Web アプリ作ってみた

## はじめに
業務では普段Javaの独自フレームワークやSVN、Db2といったレガシー&ニッチな技術スタックで開発をしているため、新しい技術を自分で追っかけていたりするのですが、今回初めて使ってみたい技術を詰め込んで Web アプリを作ってみました。

記事を書くことにも不慣れなため、お見苦しい部分が多々あるかと思いますが、間違い等あればコメントいただけると泣いて喜びます。

## 使用した技術
### 言語
- バックエンド（Go, Gin-Gonic）
- フロントエンド（Nuxt.js）

### インフラ
- AWS（VPC, Route53, ALB, ACM, EKS, S3, CloudFront, CloudWatch）
- コンテナ（Docker, docker-compose）
- データベース（MySQL）
- Kubernetes
- Terraform
- CI/CD（GitHub Actions）

ソースコードは[こちら](https://github.com/nigoroku/amb-front)
※詳細は README.md にも載せています

## アーキテクチャ図

![構成図](https://user-images.githubusercontent.com/72080660/102783682-a978d080-43de-11eb-9742-b8def9fea9c9.png "")

## ポイント解説
### Terraform
初めはネットの記事をコピペして見様見真似で動かしていましたが、やはりちゃんと理解していないと問題が発生したときにドン詰まる。。

まずは、プロバイダーを定義する main.tf ファイルを用意します。
この場合、terraform の 0.12.9 バージョンを使用することになります。
「=」を「>=」にするとそのバージョン以上という意味になる。
※Terraform 0.12から構文が変わっているとのことなので、調査する時もバージョンをチェックすることを忘れないで。

```main.tf
terraform {
  required_version = "= 0.12.9"
}
```

awsのバージョンやリージョンはこちらのprovider.tfで定義しています。

```provider.tf
provider "aws" {
  version = "3.0"
  region  = var.region
  profile = var.aws_profile
}
provider "local" {
  version = "2.0.0"
}
provider "tls" {
  version = "3.0.0"
}
```

次に変数を定義する variables.tf を用意します。
よく使う値は変数にいれてここで定義しました。
AWS のクレデンシャル情報は直接記述せずに、~/.aws/credentials に定義してそれを参照するようにしました。

```variables.tf
variable "region" {
  default = "us-east-1"
}

variable "aws_profile" {
  type        = string
  description = "AWSのプロファイル名"
  default     = "terraform-eks"
}

variable "project" {
  default = "eks"
}

variable "environment" {
  default = "dev"
}

variable "vpc_cidr_block" {
  default = "10.0.0.0/16"
}

variable "num_subnets" {
  default = 3
}

variable "key_name" {
  type    = "string"
  default = "ambitious_key"
}

locals {
  base_tags = {
    Project     = var.project
    Terraform   = "true"
    Environment = var.environment
  }

  default_tags    = merge(local.base_tags, map("kubernetes.io/cluster/${local.cluster_name}", "shared"))
  base_name       = "${var.project}-${var.environment}"
  cluster_name    = "${local.base_name}-amb-cluster"
  cluster_version = "1.18"

  public_key_file  = "./${var.key_name}.id_rsa.pub"
  private_key_file = "./${var.key_name}.id_rsa"
}
```

各AWSのリソースごとにtfファイルを定義します。
eksの場合は、kubeconfigとconfigmapをアウトプットするためにlocalに格納します。

```eks.tf
locals {
  kubeconfig = <<KUBECONFIG
apiVersion: v1
clusters:
- cluster:
    server: ${aws_eks_cluster.eks-cluster.endpoint}
    certificate-authority-data: ${aws_eks_cluster.eks-cluster.certificate_authority.0.data}
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: aws
  name: aws
current-context: aws
kind: Config
preferences: {}
users:
- name: aws
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1alpha1
      command: aws-iam-authenticator
      args:
        - "token"
        - "-i"
        - "${local.cluster_name}"
KUBECONFIG

  eks_configmap = <<CONFIGMAPAWSAUTH
apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
    - rolearn: ${aws_iam_role.eks-node-role.arn}
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes
CONFIGMAPAWSAUTH
}

resource "aws_eks_cluster" "eks-cluster" {
  name     = local.cluster_name
  role_arn = aws_iam_role.eks-master-role.arn
  version  = local.cluster_version

  vpc_config {
    security_group_ids     = [aws_security_group.cluster-master.id]
    subnet_ids             = aws_subnet.subnet.*.id
    endpoint_public_access = true
  }

  depends_on = [
    aws_iam_role_policy_attachment.eks-cluster-policy,
    aws_iam_role_policy_attachment.eks-service-policy,
  ]
}

resource "aws_eks_node_group" "eks-cluster-node-group" {
  cluster_name    = aws_eks_cluster.eks-cluster.name
  node_group_name = "eks-cluster-node-group"
  node_role_arn   = aws_iam_role.eks-node-role.arn
  subnet_ids      = aws_subnet.subnet.*.id

  scaling_config {
    desired_size = 1
    max_size     = 1
    min_size     = 1
  }
}
```

リソースファイルを全て定義し終えたら、デプロイします。

```

terraform plan
terraform apply
```

apply に成功したら terraform output の結果をそれぞれファイルに保存します。

EKS ConfigMap → manifests/config_map.yml
kubectl config → .kube/config

```
export KUBECONFIG='.kube/config'
kubectl apply -f manifests/config_map.yml
```

ノードがReadyになって居ることを確認する｡

```
kubectl get nodes
```

## まとめ

現在の会社では、Azureを使用するプロジェクトが多いため、どのクラウドでも同じように使えるTerraformを採用しました。
実装している時はトライアンドエラーで進めていましたが、applyとdestroyには結構時間がかかるため、必要な部分だけ動作をテストするみたいなことが上手くできればよかったなぁと、振り返って思いました。もう少し勉強は必要ですが、実務でも今後導入していく予定です。

残りのGo,AWS,Nuxt.jsについては、後々別記事に書きます。

## 参考

実装時はこちらの記事を参考にさせていただきました。

### Terraform

[5分で分かるTerraform（Infrastructure as Code）](https://www.lac.co.jp/lacwatch/service/20200903_002270.html)

[TerraformでEKS環境を構築する](https://qiita.com/samskeyti/items/5855f1f2b5262e27af6e)

[Terraformを使ってEKSを作成してみた](https://dev.classmethod.jp/articles/terraform-eks-create/)

[tfenvでTerraformのバージョン管理をする](https://qiita.com/kamatama_41/items/ba59a070d8389aab7694)

[Managed Node Groupsを用いてTerraformでEKSクラスタをシュッと構築する](https://tech.griphone.co.jp/2020/01/22/eks-managed-node-groups/)

[Terraformを0.11から0.12に移行するときの注意点とは](https://www.apps-gcp.com/terraform-011-to-012)

[Terraform職人入門: 日々の運用で学んだ知見を淡々とまとめる](https://www.notion.so/Terraform-c1858c752a5f458e8d7ae496a5456890#0000f881298441a596aec3fdf3e02059)