diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..1bc718c --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,45 @@ +name: Deploy + +on: + push: + branches: + - main + paths: + - .github/workflows/deploy.yml + - 'k8s/**' + pull_request: + branches: + - main + paths: + - .github/workflows/deploy.yml + - 'k8s/**' + +jobs: + deploy: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./k8s + permissions: + id-token: write + contents: read + pull-requests: write + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ vars.AWS_IAM_ROLE }} + aws-region: ${{ vars.AWS_REGION }} + + - name: Update kubeconfig + #if: github.ref == 'refs/heads/main' && github.event_name == 'push' + run: aws eks update-kubeconfig --name ${{ vars.AWS_EKS_CLUSTER_NAME }} --region ${{ vars.AWS_REGION }} + + - name: Deploy to EKS + #if: github.ref == 'refs/heads/main' && github.event_name == 'push' + run: | + kubectl apply diff --git a/k8s/deployment.yml b/k8s/deployment.yml new file mode 100644 index 0000000..5f07b27 --- /dev/null +++ b/k8s/deployment.yml @@ -0,0 +1,84 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: healthmed-deployment + namespace: healthmed + labels: + app: healthmed +spec: + replicas: 1 + strategy: + type: RollingUpdate + selector: + matchLabels: + app: healthmed + template: + metadata: + namespace: healthmed + labels: + app: healthmed + spec: + serviceAccountName: healthmed-service-account + volumes: + - name: secrets-store-inline + csi: + driver: secrets-store.csi.k8s.io + readOnly: true + volumeAttributes: + secretProviderClass: healthmed-aws-secrets + containers: + - name: healthmed + image: 202062340677.dkr.ecr.us-east-1.amazonaws.com/fiap-3soat-g15-healthmed:latest + imagePullPolicy: Always + ports: + - containerPort: 8080 + resources: + requests: + cpu: "100m" + limits: + cpu: "200m" + volumeMounts: + - name: secrets-store-inline + mountPath: "/mnt/secrets-store" + readOnly: true + livenessProbe: + httpGet: + path: /actuator/health + port: 8080 + periodSeconds: 30 + failureThreshold: 10 + initialDelaySeconds: 20 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: /actuator/health + port: 8080 + periodSeconds: 30 + failureThreshold: 10 + initialDelaySeconds: 20 + timeoutSeconds: 5 + env: + - name: SPRING_PROFILES_ACTIVE + value: live + - name: DB_ENDPOINT + valueFrom: + secretKeyRef: + name: healthmed-db-secrets + key: endpoint + - name: DB_NAME + valueFrom: + secretKeyRef: + name: healthmed-db-secrets + key: name + - name: DB_USERNAME + valueFrom: + secretKeyRef: + name: healthmed-db-secrets + key: username + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: healthmed-db-secrets + key: password + - name: ADMIN_ACCESS_TOKEN + value: token diff --git a/k8s/hpa.yml b/k8s/hpa.yml new file mode 100644 index 0000000..9db67ab --- /dev/null +++ b/k8s/hpa.yml @@ -0,0 +1,13 @@ +apiVersion: autoscaling/v1 +kind: HorizontalPodAutoscaler +metadata: + name: healthmed-hpa + namespace: healthmed +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: healthmed-deployment + minReplicas: 1 + maxReplicas: 4 + targetCPUUtilizationPercentage: 50 diff --git a/k8s/secret-provider.yml b/k8s/secret-provider.yml new file mode 100644 index 0000000..2bad145 --- /dev/null +++ b/k8s/secret-provider.yml @@ -0,0 +1,35 @@ +apiVersion: secrets-store.csi.x-k8s.io/v1 +kind: SecretProviderClass +metadata: + name: healthmed-aws-secrets + namespace: healthmed +spec: + provider: aws + secretObjects: + - secretName: healthmed-db-secrets + type: Opaque + data: + - objectName: endpoint + key: endpoint + - objectName: name + key: name + - objectName: username + key: username + - objectName: password + key: password + parameters: + region: us-east-1 + objects: | + - objectName: "/live/healthmed/db" + objectType: "ssmparameter" + jmesPath: + - path: "endpoint" + objectAlias: "endpoint" + - path: "name" + objectAlias: "name" + - objectName: "arn:aws:secretsmanager:us-east-1:202062340677:secret:rds!db-722fbc15-eb85-4300-8fc3-221bb33b0d14-taiDvT" + jmesPath: + - path: "username" + objectAlias: "username" + - path: "password" + objectAlias: "password" diff --git a/k8s/service.yml b/k8s/service.yml new file mode 100644 index 0000000..7cb0978 --- /dev/null +++ b/k8s/service.yml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + name: healthmed-load-balancer + namespace: healthmed + labels: + app: healthmed + annotations: + service.beta.kubernetes.io/aws-load-balancer-name: healthmed-load-balancer + service.beta.kubernetes.io/aws-load-balancer-type: external + service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing + service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: instance +spec: + type: LoadBalancer + selector: + app: healthmed + ports: + - name: http + protocol: TCP + port: 80 + targetPort: 8080 diff --git a/terraform/eks.tf b/terraform/eks.tf new file mode 100644 index 0000000..04593b5 --- /dev/null +++ b/terraform/eks.tf @@ -0,0 +1,213 @@ +locals { + clustername = "healthmed" + cluster_version = "1.29" + app_namespace = "healthmed" +} + +data "aws_caller_identity" "current" {} + +# CLUSTER + +// https://github.com/terraform-aws-modules/terraform-aws-eks +module "eks" { + source = "terraform-aws-modules/eks/aws" + version = "20.8.3" + + cluster_name = local.clustername + cluster_version = local.cluster_version + cluster_endpoint_private_access = true + cluster_endpoint_public_access = true + + access_entries = { + root_admin = { + kubernetes_groups = [] + principal_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" + + policy_associations = { + admin = { + policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" + access_scope = { + type = "cluster" + } + } + } + } + } + + enable_cluster_creator_admin_permissions = true + + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets + + eks_managed_node_group_defaults = { + disk_size = 20 + instance_types = ["t3.small"] + } + + eks_managed_node_groups = { + default_node_group = { + use_custom_launch_template = false + + desired_size = 2 + min_size = 1 + max_size = 5 + + capacity_type = "SPOT" + } + } + + cluster_addons = { + // Service Discovery + coredns = { + most_recent = true + } + } +} + +# NAMESPACES + +# Creating as Terraform resource (instead of Kubernetes manifest) +# for removing Kubernetes services (like load balancers) when destroying it +resource "kubernetes_namespace" "app-namespace" { + metadata { + name = "app" + annotations = { + name = "app" + } + } +} + +# ADD-ONS + +# Install AWS Secrets and Configuration Provider (ASCP) +# https://docs.aws.amazon.com/secretsmanager/latest/userguide/integrating_csi_driver.html +resource "helm_release" "csi_secrets_store" { + name = "csi-secrets-store" + repository = "https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts" + chart = "secrets-store-csi-driver" + namespace = "kube-system" + + set { + name = "syncSecret.enabled" + value = "true" + } + + set { + name = "enableSecretRotation" + value = "true" + } + + depends_on = [ + module.eks + ] +} + +resource "helm_release" "secrets_provider_aws" { + name = "secrets-provider-aws" + repository = "https://aws.github.io/secrets-store-csi-driver-provider-aws" + chart = "secrets-store-csi-driver-provider-aws" + namespace = "kube-system" + + depends_on = [ + module.eks, + helm_release.csi_secrets_store + ] +} + +# Install AWS Load Balancer Controller +# https://docs.aws.amazon.com/eks/latest/userguide/aws-load-balancer-controller.html +# https://github.com/aws/eks-charts/tree/master/stable/aws-load-balancer-controller +resource "helm_release" "aws_load_balancer_controller" { + name = "aws-load-balancer-controller" + repository = "https://aws.github.io/eks-charts" + chart = "aws-load-balancer-controller" + namespace = "kube-system" + + set { + name = "region" + value = var.region + } + + set { + name = "vpcId" + value = module.vpc.vpc_id + } + + set { + name = "clusterName" + value = local.clustername + } + + set { + name = "serviceAccount.create" + value = false + } + + set { + name = "serviceAccount.name" + value = "kube-system-service-account" + } +} + +# ROLES ATTACHED TO SERVICE ACCOUNTS + +module "kube_system_service_account_role" { + source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks" + role_name = "HealthMedKubeSystemServiceAccount" + + oidc_providers = { + main = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = [ + "kube-system:kube-system-service-account", + ] + } + } + + attach_load_balancer_controller_policy = true + + tags = var.tags +} + +module "app_service_account_role" { + source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks" + role_name = "HealthMedServiceAccount" + + oidc_providers = { + main = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = [ + "${local.app_namespace}:${local.app_namespace}-service-account", + ] + } + } + + role_policy_arns = { + HealthMedRDSSecretsReadOnlyPolicy = aws_iam_policy.rds_secrets_read_only_policy.arn + HealthMedRDSParamsReadOnlyPolicy = aws_iam_policy.rds_params_read_only_policy.arn + } + + tags = var.tags +} + +# SERVICE ACCOUNTS + +resource "kubernetes_service_account" "kube_system_service_account" { + metadata { + name = "kube-system-service-account" + namespace = "kube-system" + annotations = { + "eks.amazonaws.com/role-arn" = module.kube_system_service_account_role.iam_role_arn + } + } +} + +resource "kubernetes_service_account" "app_service_account" { + metadata { + name = "${local.app_namespace}-service-account" + namespace = local.app_namespace + annotations = { + "eks.amazonaws.com/role-arn" = module.app_service_account_role.iam_role_arn + } + } +} diff --git a/terraform/providers.tf b/terraform/providers.tf index 71fb950..4d1365c 100644 --- a/terraform/providers.tf +++ b/terraform/providers.tf @@ -16,3 +16,27 @@ terraform { provider "aws" { region = var.region } + +provider "kubernetes" { + host = module.eks.cluster_endpoint + cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data) + + exec { + api_version = "client.authentication.k8s.io/v1beta1" + command = "aws" + args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name] + } +} + +provider "helm" { + kubernetes { + host = module.eks.cluster_endpoint + cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data) + + exec { + api_version = "client.authentication.k8s.io/v1beta1" + command = "aws" + args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name] + } + } +} diff --git a/terraform/rds.tf b/terraform/rds.tf index 6c0d8f9..60824a5 100644 --- a/terraform/rds.tf +++ b/terraform/rds.tf @@ -63,7 +63,7 @@ module "rds_params" { } resource "aws_iam_policy" "rds_secrets_read_only_policy" { - name = "HealthmedRDSSecretsReadOnlyPolicy" + name = "HealthMedRDSSecretsReadOnlyPolicy" policy = jsonencode({ Version = "2012-10-17" @@ -81,7 +81,7 @@ resource "aws_iam_policy" "rds_secrets_read_only_policy" { } resource "aws_iam_policy" "rds_params_read_only_policy" { - name = "HealthmedRDSParamsReadOnlyPolicy" + name = "HealthMedRDSParamsReadOnlyPolicy" policy = jsonencode({ Version = "2012-10-17"