オブジェクトの広場はオージス総研グループのエンジニアによる技術発表サイトです

コンテナ・マイクロサービス

Kubernetes活用への道のり

第1回 環境構築をコード化し、管理・運用を楽にしよう
オージス総研 技術部 アドバンストテクノロジセンター
山中 克容
2021年8月19日

これまでモノリシックなシステム開発をしてきた会社やチームがKubernetesやその上でのマイクロサービスの開発に取り組むにはいくつか壁があると思います。その壁はどのようにサービス分割するかや会社・チームの体制といった話から、環境の準備やアプリケーションアーキテクチャのような話まで多岐に渡ります。これからKubernetesやマイクロサービスに踏み出したくても手探りではリスクが大きく感じられ、一歩が踏み出せずこれまで通りのやり方で開発を続けられている方も多いのではないかと思います。
しかしビジネスの変化が激しい昨今、これまで以上に素早いリリースや継続的な変更を求められるケースも増えてきており、サーバレスやコンテナの活用が有効な案件が増えてきていることもまた事実です。
そこで本連載ではKubernetesやマイクロサービスを活用するにあたりどんな準備を進めておけばいいか整理します。第1回目はコードによる環境構築についてご紹介します。

1. はじめに

まずKubernetesの活用やマイクロサービス開発における課題を考えてみます。 従来のモノリシックアプリケーションを開発してきたチームが新しくKubernetesやマイクロサービスに取り組むとき、いろいろな課題や不安があると思います。 いくつか例を挙げてみると以下のようなものでしょうか。

  • どんなシステムがマイクロサービスにあってるの?
  • どういう基準でサービスを分割する?
  • 環境準備が大変じゃない?
  • どんなミドルウェアを組合せたらいい?
  • どうやって開発・テストをすればいい?
  • 非同期通信や分散処理ってどうやる?いつ使う?
  • システムが複雑化するけど運用できる?

こういった不安が払拭できないと、メリットよりもデメリットが大きく感じられ、開発手法を変えるのが難しくなったりします。 本連載ではこれらの不安について少しでも払拭するための取り組み内容をご紹介していきます。

2. 本連載で取り組む範囲

前述の課題を見たとき、どんなシステムがマイクロサービスにあってるかやサービス分割方法などは上流工程の課題となります。 上流工程の課題解決は別に機会があれば連載することにし、本連載では環境準備以降の課題についてフォーカスします。 具体的には以下のようなトピックを取り上げる予定です。

  • TerraformやArgoCDを活用した環境構築の自動化
  • SkaffoldやTelepresenceを活用した開発の効率化
  • QuarkusやDaprを活用したマイクロサービス開発
  • Istio/Prometheus/Grafanaを活用した可視化

なお、本連載では実行環境としてAWSを前提としますので、サンプルコードの実行にはAWSのアカウントが必要となります。

AWSでKubernetesを動作させる場合は無料枠での利用はできません。コストを計算の上、発生する費用をご理解してご利用ください。(検証終了時の環境削除なども忘れないようにしてください) 本記事で記載しているコードの実行のために発生した費用に関して、オージス総研ならびにオブジェクトの広場編集部は一切責任を持ちません。

3. Infrastructure as Code(IaC)

連載第1回目では「環境準備が大変じゃない?」という不安に対して、Infrastructure as Code(IaC)で解決を図ります。
Infrastructure as Codeとはインフラ環境の構築や管理を手動のプロセスではなくコードを使用して行うことを言います。クラウド環境はブラウザコンソールやCLIが充実しているので簡単に作るだけならコード化しなくても簡単に環境は作れます。個人で試す環境を作るだけならコード化する必要性はないでしょう。 しかし、同一環境の再現や複製、また複数の環境に同一の変更を加えるなど、運用面を考えたとき手動作成では後々負担が大きくなります。 またコードをGitで管理することで、いつ誰がどういう変更をしたかを管理することも容易に可能となります。 環境構築手順をコード化してGitで管理することで、少ない負担で適切に管理・運用できるようにしていきましょう。

今回、以下の構築を進めていきます。

  • Terraformを使い、AWS上にAWSマネージドサービスのKubernetesであるAmazon Elastic Kubernetes Service(※ 以降EKSと記載))を構築
  • EKSにArgoCDを導入(ArgoCDについては後述)
  • ArgoCDを使いEKS上に代表的なミドルウェアを導入

また、環境ができたら最後にサンプルアプリケーションのGitOpsによるデプロイを行ってみます。

本記事では各技術要素(AWS/Terraform/Kubernetes/ArgoCD/Github/GithubActions等)の基本的な概要などについては記載しておりません。適宜各公式ドキュメントを参照してください。

4. Kubernetes(EKS)の構築

それではKubernetes(EKS)を構築していきましょう。具体的にはAmazon Virtual Private Cloud(以降VPCと記載)等AWSのネットワーク周りとKubernetes(EKS)の構築になります。

今回はHashicorp社のTerraformを活用してインフラのコード化を実現します。 TerraformはAWSのCloudFormationと同様のIaCを実現するためのツールです。一番の特徴は複数のクラウド環境(AWS,Azure,GCP,OracleCloudなど)でも利用ができるところにあります。 また、TerraformRegistryに多数の公開モジュールもありますので、それらをベースにカスタマイズすることでかなりの部分が簡単に構築できるようになっています。

今回はTerraformCLIのVersion1.0.1を使用して構築を進めます(CLIのダウンロードはこちら)が、CLIだけでなくTerraformCloud(Free/有償)などもありますので、各環境に応じて最適なものを選択してください。

前提条件

本記事のコードを実行するには事前にAWSCLIを実行環境に導入し、CLIでAWSへアクセスできることを確認してください。 また、環境変数AWS_PROFILEおよびAWS_REGIONに適切な設定を記述してください。 (AWSの接続定義についてはAuthenticationを参照してください)

構成図

今回作成する環境は以下になります。

AWS

コードの構成

VPCおよびEKSを構築するコードは下記構成になります。

terraform
├ main.tf
└ modules
  ├ eks
  │  ├ main.tf
  │  └ variables.tf
  └ vpc
     ├ main.tf
     └ outputs.tf

各ファイルの役割は以下のようになっています。

  • main.tf
    • リソースを作成するモジュール定義を記述
  • variables.tf
    • モジュールへのインプットとなる変数(関数の引数のようなもの)
  • outputs.tf
    • モジュールの結果を外部にアウトプットする変数(関数の戻り値のようなもの)

それでは各ファイルの内容を簡単に見ていきます。
なお、今回VPCやEKSの構築には公式モジュールを利用しています。詳細な仕様は公式ドキュメントを参照してください。

Terraformの記述言語

Terraformで利用するコードはHCL (HashiCorp Configuration Language)というHashicorp社で作られている設定言語を用います。 HCLはコマンドラインツールで使用するために人間からも機械からも扱いやすいように構成されていて、主にDevOpsツールやサーバーなどを対象としているとのことです。 YAMLやJSONといった使い慣れた記述ではないですが、特に難しいわけではありませんのでドキュメントを参照しながら記述すれば初めての方でも取り組めるかと思います。

terraform/vpc/main.tf

VPCを構築する設定になります。公式のモジュールサンプルをほぼそのまま利用しています。 VPC名やCIDR、アベイラビリティゾーン、サブネットなどの値は適宜変更してください。 今回は大阪リージョンに構築するため、アベイラビリティゾーンに大阪リージョンのものを指定しています。

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"
  version = "3.1.0"

  name = "sample-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["ap-northeast-3a", "ap-northeast-3b"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24"]

  enable_nat_gateway = true
  single_nat_gateway  = true    # 検証用なのでコストを抑えるためNATGatewayは1つにする
  enable_vpn_gateway = false    # 今回必要ないためfalse

  tags = {
    Terraform = "true"
    Environment = "dev"
  }
}

terraform/vpc/outputs.tf

eksモジュール側で利用する値を出力しておきます。

output "vpc_id" {
  value = module.vpc.vpc_id
}
output "private_subnets" {
  value = module.vpc.private_subnets
}

terraform/eks/main.tf

EKSを構築する設定になります。
こちらも公式のサンプルがベースになりますが、worker_groupsはnode_groupsに変更し、ノード(EC2)の管理をマネージド型にしています。

data "aws_eks_cluster" "cluster" {
  name = module.my-cluster.cluster_id
}

data "aws_eks_cluster_auth" "cluster" {
  name = module.my-cluster.cluster_id
}

terraform {
  required_providers {
    kubernetes = {
      source = "hashicorp/kubernetes"
      version = "2.3.2"
    }
  }
}

provider "kubernetes" {
  host                   = data.aws_eks_cluster.cluster.endpoint
  cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data)
  token                  = data.aws_eks_cluster_auth.cluster.token
}

module "my-cluster" {
  source          = "terraform-aws-modules/eks/aws"
  version         = "17.1.0"

  cluster_name    = "my-cluster"
  cluster_version = "1.20"
  subnets         = var.private_subnets
  vpc_id          = var.vpc_id

  node_groups = {
    my-nodegroup = {
      desired_capacity = 2
      max_capacity     = 2
      min_capacity     = 2
      instance_types   = ["t3.small"]
    }
  }
}

instance_typesにt3.microを指定した場合、1ノードにつき4Podが上限になります。EKS自体は起動しますが(起動直後にデプロイされているPodは6個)本記事のサンプル動作のためには最小でもt3.smallにする必要があります。(もしくはノード数の上限を3以上)

※ PodとはKubernetesアプリケーションの基本的な実行単位(1つ以上のコンテナやその他必要なリソース、実行方法などをカプセル化したもの)

なお、Nodeのスペックは下記コマンドで確認できます。(Podに割り当て可能なリソースは戻り値の中のallocatable内に記載されています)

$ kubectl get nodes -o yaml

...
    allocatable:
      attachable-volumes-aws-ebs: "25"
      cpu: 1930m
      ephemeral-storage: "95551679124"
      hugepages-1Gi: "0"
      hugepages-2Mi: "0"
      memory: 1507636Ki
      pods: "11"
    capacity:
...

terraform/eks/variables.tf

VPCからの出力内容を受け取るための変数になります。

variable "vpc_id" {}
variable "private_subnets" {}

terraform/main.tf

最後にmainではvpcおよびeksのモジュールを読み込んでいます。 またeks側はVPC側で出力した値を変数にセットしておきます。

module "vpc" {
    source = "./modules/vpc"
}

module "eks" {
    source = "./modules/eks"

    vpc_id          = module.vpc.vpc_id
    private_subnets = module.vpc.private_subnets
}

以上で基本的な設定は完了になります。実行してEKSを構築してみましょう。

VPCおよびEKSの構築

# 必要モジュールのインストール
$ terraform init

# 実行計画
$ terraform plan

# 実際に構築されるリソースの一覧が表示されます。
# 具体的には以下のリソースの作成計画が表示されます。

---
VPC
パブリックサブネット
プライベートサブネット
NATゲートウェイ
Internetゲートウェイ
ルートテーブル
ElasticIP
EKSクラスタ
EKSノードグループ
EKS用IAM設定
EKS用セキュリティグループ
EKS設定(kubeconfig,configmap)
---

# 実行
$ terraform apply

実行時にも再度リソースの作成計画が表示されます。問題なければ「yes」と入力して進めましょう。

※ クラスタの作成に時間がかかるため、構築完了までには10分から30分程度かかります。

Kubernetesの動作確認

ローカル端末にkubectlを導入して動作確認を行います。

Kubernetes公式サイトを参考にkubectlをローカル端末に導入します。

EKSに接続します。

# EKSへの接続情報を作成(ユーザのホーム配下に.kube/configファイルが作られ、その中に接続情報やユーザ情報が記載されます)
$ aws eks --region ap-northeast-3 update-kubeconfig --name my-cluster

# contextが構築したEKSに向いていることを確認
$ kubectl config get-contexts
CURRENT   NAME                   CLUSTER                AUTHINFO                  NAMESPACE
*         xxxxxxxxx/my-cluster   xxxxxxxxx/my-cluster   xxxxxxxxx/my-cluster

# 動作確認としてネームスペース一覧を表示
$ kubectl get namespaces
NAME              STATUS   AGE
default           Active   54m
kube-node-lease   Active   54m
kube-public       Active   54m
kube-system       Active   54m

kubectlの動作が確認できれば、EKSの導入までは完了となります。

5. ArgoCDおよびArgoCDを利用したミドルウェアの導入

Kubernetes上でアプリケーションを稼働させる場合、Kubernetesとアプリケーションだけで完結することはほとんどありません。何かしらのミドルウェアと協調動作することになると思います。 このミドルウェアもコードから導入するようにしておくと環境の再現や複製時の手間が大幅に削減できます。 今回ArgoCDを利用して、ミドルウェアもGitを中心に管理できる環境(GitOps)を構築します。

GitOps

GitOpsとはWeaveworksが最初に提唱した言葉になります。(Guide To GitOps)
概略としては、インフラおよびアプリケーションの構成をGitを使って管理する一連の仕組みになります。 開発者や運用者はシステムの構成情報をコード化してGitで管理します。Gitの内容が更新されるとシステムは差分を検知し自身の環境に反映します。こうすることで構成管理の一元化や変更履歴の追従、デプロイやロールバックの簡素化などのメリットを享受することができます。 GitOpsを実現するためのデリバリーツールとしては、ArgoCD、PipeCD、Flux、JenkinsXなどがあります。

ArgoCD

ArgoCDを公式サイトで見ると、「ArgoCDとは宣言型であり、Kubernetesのための宣言型デリバリーツール」とあります。 ArgoCDはKubernetes内からGitリポジトリおよび実行環境を監視することで常に実行環境をGitリポジトリと同じ状態に保つように動作します。 またKubernetesのマニフェストだけでなくHelmやKustomizeなどにも対応しているため、アプリケーションだけでなくミドルウェアなどの管理も容易に可能です。

ArgoCDの導入

今回は一番基本的な導入手順(HA構成などは考慮しない)でstable版を導入します。(執筆時点でのバージョンはv2.0.4)

$ kubectl create namespace argocd
$ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

ArgoCDの動作確認

ローカル端末から確認する場合、ポートフォワードを利用します。

$ kubectl port-forward svc/argocd-server -n argocd 8443:443

ブラウザでhttps://localhost:8443にアクセスしてみましょう。

ArgoCD

以下でログインできます。

Usrename : admin
Password : 以下のsecretに記載(Windowsはbase64コマンドがないため、パイプ「|」以降は書かずにエンコード文字列を出力して他の方法でデコードしてください ※WSLであればそのまま実行可)

$ kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d

ログインできたら導入まで完了です。

ArgoCDのマニフェスト

ArgoCDによるGitOpsを実現するにはArgoCDのカスタムリソースであるApplicationを作成します。
Applicationリソースでは基本的に下記の3つを設定します。

  • 導入元(リポジトリパス)
  • 導入先(Kubernetesおよびnamespace)
  • 同期ポリシー(自動か手動かなど)

※ 他にグループ化するためのAppProjectなどのリソースもありますが、今回は割愛します。詳細な設定内容についてはマニュアルをご参照ください。

今回はサンプルとしてHelmチャートで公開されているNginxIngressControllerを導入してみます。

※ IngressControllerはクラスタ外からKubernetes内のコンテナにアクセスする際に必要となるリソースです。EKSでは実体としてELBが作成されます。

(参考)Helm

HelmはKubernetesのパッケージマネージャです。Linuxのyumやaptなどに該当するものになります。 Helmはオープンソースソフトウェアとして公開されており、様々なパッケージ(Chart)が公開されています。 依存関係含めパッケージングされているので1コマンドで簡単に導入できる点や、導入時の環境に合わせてパラメータ指定して導入できるなどの特徴があります。

NginxIngressControllerの導入

サンプルのApplicationリソースは下記になります。

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: nginx
  namespace: argocd
spec:
  project: default
  source:
    repoURL: 'https://helm.nginx.com/stable'
    targetRevision: 0.10.0
    chart: nginx-ingress
  destination:
    server: 'https://kubernetes.default.svc'
    namespace: nginx
  syncPolicy:
    automated:
      prune: true
      selfHeal: false
    syncOptions:
      - CreateNamespace=true

Nginx公式のHelmチャートを利用しています。

このApplicationリソースをKubernetesに反映します。

# ファイル名「application.yaml」で保存している場合
$ kubectl apply -f application.yaml

反映するとArgoCD上デプロイされているリソースが確認できます。

nginx

また、AWSの管理コンソールでロードバランサが作成されていることが確認できます。

6. GitOpsへの発展

ここまででミドルウェアをコードから導入することができるようになりました。 ただし先ほどの手順ではApplicationリソースをkubectl applyで反映していますので、例えばNginxIngressControllerを新バージョンにアップグレードしたい時などは、Applicationリソースを変更の上kubectl applyを再度実行する必要があります。 次はこのApplicationリソースをGitリポジトリに登録し、リポジトリ上のファイルが更新されたら自動的にKubernetesに反映されるようにしてみましょう。

Applicationリソースの階層化

ArgoCDはApplicationリソースのsourceで指定したリソースとdestinationで指定したデプロイ済みの情報を比較して、差分があれば同期をとります。 そのため、Gitリポジトリ上のApplicationリソースの更新を自動検知させたい場合は、Applicationリソースを束ねるApplicationリソースを作ります。(App of Appsパターン)

構成は下記のようになります。

argocd
├ root.yaml
└ apps
  └  application.yaml   ← 先ほど作成したファイルはこれ

root.yaml

NginxIngressControllerを導入するApplicationリソースの上にもうひとつ新しくApplicationリソースを作成します。

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: ecosystem-root
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/xxxxxx # 各自のリポジトリ
    targetRevision: main               # ブランチ名がmainの場合
    path: 'argocd/apps/'               # 検知対象のリソースが置かれたパス
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    automated:
      prune: true

spec.sourceに先ほど作成したNginxIngressControllerを導入するApplicationリソースを配置したリポジトリを指定します。

階層化されたマニフェストの導入

root.yamlをkubernetesに導入します。

$ kubectl apply -f argocd/root.yaml

ArgoCDで確認するとecosystem-rootに紐づいてNginxIngressControllerが導入されていることが確認できます。

ecosystems

NginxIngressControllerのバージョンを更新したい時などはGit上のファイルを更新するだけでArgoCDが差分検知し導入まで自動で行います。 これで、Kubernetes上に何がデプロイされているかGitを管理するだけで把握できるようになりましたね。

また、このような階層構造にすることで意味のある単位でグルーピングした運用なども行えます。独立して運用するのではなく他と協調してデプロイさせたい場合などでApp of Appsパターンを検討してみてください。

7. サンプルアプリケーションのGitOpsによるデプロイ

それでは第1回の最後としてサンプルアプリケーションをデプロイして動作確認してみましょう。 サンプルアプリケーションとしてQuarkusのgetting-startedアプリケーションをデプロイします。

構成は以下のようになります。

gitops

インフラにコンテナレジストリを追加

まずはインフラを一部更新します。最初の構築ではVPC関連とEKSのみ構築していたので、そこにコンテナレジストリ(Amazon Elastic Container Registry(以降ECRと記載))を追加します。

terraform
├ main.tf
└ modules
  ├ ecr         ← ecrを追加
  │  └ main.tf
  ├ eks
  │  ├ main.tf
  │  └ variables.tf
  └ vpc
     ├ main.tf
     └ outputs.tf

terraform/ecr/main.tf

サンプルアプリケーション用のコンテナレジストリを追加します。

resource "aws_ecr_repository" "ecr_getting-started" {
  name                 = "getting-started"
  image_tag_mutability = "MUTABLE"

  image_scanning_configuration {
    scan_on_push = true
  }
}

terraform/main.tf

module ecrモジュールの読み込みを追加します。

module "vpc" {
    source = "./modules/vpc"
}

module "eks" {
    source = "./modules/eks"

    vpc_id          = module.vpc.vpc_id
    private_subnets = module.vpc.private_subnets
}

module "ecr" {
    source = "./modules/ecr"
}

ECRの構築

モジュールが増えたのでterraform initから再実行します。

# 必要モジュールのインストール
$ terraform init

# 実行計画(ECRの追加分が差分として出力されます)
$ terraform plan

# 実行
$ terraform apply

実行時にも再度リソースの作成計画が表示されます。問題なければ「yes」と入力して進めましょう。

サンプルアプリケーション用マニフェストファイル

次にアプリケーションをEKSにデプロイするためのマニフェストをGithubに登録します。

マニフェストは以下の3つになります。

  • Deployment(deployment.yaml)
    • Pod(コンテナ)をどのようにデプロイするか定義したマニフェスト
  • Service(service.yaml)
    • Pod(コンテナ)へのアクセスを定義したマニフェスト
  • Ingress(ingress.yaml)
    • クラスタ外からのアクセスを定義したマニフェスト

また、登録リポジトリは下記と仮定します。

https://github.com/<ユーザー名>/getting-started-manifest

depoyment.yaml

getting-startedアプリケーションはデフォルトでは8080番ポートで起動するため、8080番ポートで待ち受けるPodを作成します。
また、サンプルですので冗長化はなくてもいいので、replicas: 1としています。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: getting-started-deployment
  labels:
    app: getting-started
spec:
  replicas: 1
  selector:
    matchLabels:
      app: getting-started
  template:
    metadata:
      labels:
        app: getting-started
    spec:
      containers:
      - name: getting-started
        image: <AWSアカウントID>.dkr.ecr.ap-northeast-3.amazonaws.com/getting-started:0000000000
        ports:
        - containerPort: 8080

※ イメージタグ部はPullRequestが書き換えるため暫定値

service.yaml

8080番ポートへのアクセスをapp: getting-startedラベルを持つPodへ流します。 なお、ここではtype: ClusterIPで指定しているため外部から直接アクセスはできません。

apiVersion: v1
kind: Service
metadata:
  name: getting-started-service
  labels:
    app: getting-started
spec:
  ports:
  - name: http
    port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    app: getting-started
  type: ClusterIP
status:
  loadBalancer: {}

ingress.yaml

外部からアクセスするためのIngress設定になります。 ロードバランサの/helloパスに来たリクエストをgetting-started-serviceへ流します。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: getting-started-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - host: <ロードバランサのDNS名>
    http:
      paths:
      - path: /hello
        pathType: Prefix
        backend:
          service:
            name: getting-started-service
            port:
              number: 8080

サンプルアプリケーションの準備

最後に、サンプルアプリケーションとしてQuarkusでHelloWorldアプリケーションを準備します。
動作にはJDK11以降、Maven3.8.1以降が必要となります。(Quarkus GetStarted

GetStartedの手順に沿ってアプリケーションを準備します。

$ mvn io.quarkus:quarkus-maven-plugin:2.0.1.Final:create -DprojectGroupId=org.acme -DprojectArtifactId=getting-started -DclassName="org.acme.getting.started.GreetingResource" -Dpath="/hello"

アプリケーションの準備ができたらGithubActionsのコードをアプリケーションに追加します。

参考:GithubActionsのMavenドキュメント

.github/workflows/maven.yml

name: Java CI with Maven

on:
  push:
    branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    env:
      IMAGE_TAG: ${{ github.sha }}

    steps:
    - name: Checkout repo                                      # コードのリポジトリをチェックアウト
      uses: actions/checkout@v2
    - name: Checkout manifest repo                             # マニフェストのリポジトリをチェックアウト
      uses: actions/checkout@v2
      with:
        repository: <ユーザー名>/getting-started-manifest
        path: getting-started-manifest
    - name: Set up JDK 11                                      # JDK11のセットアップ
      uses: actions/setup-java@v1
      with:
        java-version: 11
    - name: Cache Maven packages                               # Mavenキャッシュ
      uses: actions/cache@v2
      with:
        path: ~/.m2
        key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
        restore-keys: ${{ runner.os }}-m2
    - name: Build with Maven                                   # Javaコードをビルド
      run: mvn -B package --file pom.xml -Dquarkus.package.type=fast-jar
    - name: Configure AWS credentials                          # ECRへ接続するための情報
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ap-northeast-3
    - name: Login to Amazon ECR                                # ECRへログイン
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v1
    - name: Order Build, tag, and push image to Amazon ECR     # Dockerイメージを作成し、ECRへプッシュ
      id: build-image-service
      env:
        DOCKER_BUILDKIT: 1
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        ECR_REPOSITORY: getting-started
      run: |
        cd $GITHUB_WORKSPACE
        docker build --cache-from=$ECR_REGISTRY/$ECR_REPOSITORY:latest --build-arg BUILDKIT_INLINE_CACHE=1 -t $ECR_REGISTRY/$ECR_REPOSITORY:latest -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f ./src/main/docker/Dockerfile.jvm .
        docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
        echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
    - name: pull request                                       # プルリクエスト作成
      id: pull-request
      env:
        tag: update-image-feature-${{ github.sha }}
      run: |
        cd $GITHUB_WORKSPACE/getting-started-manifest
        git checkout -b $tag
        sed -i 's/image: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.ap-northeast-3.amazonaws.com\/getting-started:\(.*\)/image: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.ap-northeast-3.amazonaws.com\/getting-started:'$IMAGE_TAG'/' ./deployment.yaml 
        git config user.name <Githubユーザー名>
        git config user.email <メールアドレス>
        git add .
        git commit -m "update manifest"
        git push --set-upstream origin $tag
        curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: token $token" "https://api.github.com/repos/<ユーザー名>/getting-started-manifest/pulls" -d '{"title": "new app deploy request", "head": "<ユーザー名>:'$tag'", "base": "main"}'

準備ができたらGithubへ登録します。

なお、登録先は下記と仮定します。

https://github.com/<ユーザー名>/getting-started

また、リポジトリには下記Secretを設定しておいてください。

  • AWS_ACCOUNT_ID
  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY

※ キーを発行しているユーザーにはECRへpushするための権限が必要です

サンプルアプリケーションのデプロイ

アプリケーションをGithubへPushしたら自動でGithubActionsが起動します。問題なければPullRequestまで作成されると思いますのでマージします。

ArgoCDのアプリケーションとしてサンプルアプリケーションのマニフェストをリリース

アプリケーションのマニフェストをArgoCDで管理し、GitOpsでデプロイするようにします。

application.yaml

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: getting-started
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/<ユーザー名>/getting-started-manifest
    targetRevision: main
    path: './'
  destination:
    server: https://kubernetes.default.svc
    namespace: default
  syncPolicy:
    automated:
      prune: true

※ URLの<ユーザー名>部分は各自のリポジトリに合わせてください

アプリケーションマニフェストを反映すると、デプロイされます。

$ kubectl apply -f application.yaml

application

動作確認

ロードバランサのURLにアクセスして確認します。

http://<ロードバランサのDNS名>/hello

Hello RESTEasy

と表示されれば正常に起動できています。

アプリケーションの更新

GitOpsのフローを回すためにアプリケーションを更新してみます。

GreetingResource.java

return文変更

return "Hello Hiroba";

GreetingResourceTest.java

テストの期待値も同様に変更

.body(is("Hello Hiroba"));

変更を反映するとビルド→コンテナをECRへプッシュ→PullRequest生成と進み、マージするとArgoCDが差分を検知しアプリケーションを更新します。
ブラウザ上の文言が

Hello Hiroba

と変更されたら成功です。

8. おわりに

今回はいつでも環境を再現できるIaCやGitOpsによる構成管理について取り上げました。
インフラ環境だけでなくKubernetes上で動作する各種ミドルウェアやアプリケーションまでGitにあるコードから導入できるようにしておくと環境の複製や再現、運用などがかなり簡単になります。最初は少し手間がかかりますが、一度作ると以降の管理がかなり楽になりますね。
環境準備ができましたので、次回はコンテナ開発の開発効率を向上させる取り組みについて考えていきたいと思います。