@@ -129,9 +129,19 @@ locals {
129129 subnet_b_cidr = "10.0.11.0/24"
130130 az_a = "us-west-2a"
131131 az_b = "us-west-2b"
132- app_name = " <your_app_name>"
132+ app_name = <your_app_name>
133133 app_source_code_path = "../../"
134134 ansible_dir = "../ansible/playbooks"
135+ app_files = fileset(local.app_source_code_path, "**")
136+
137+ image_tag = sha256(join("", [
138+ for f in local.app_files :
139+ try(filesha256("${local.app_source_code_path}/${f}"), "")
140+ if length(regexall("^deploy/", f)) == 0
141+ && length(regexall("^\\.vscode/", f)) == 0
142+ && length(regexall("^node_modules/", f)) == 0
143+ && length(regexall("^\\.gitignore", f)) == 0
144+ ]))
135145
136146 ingress_ports = [
137147 { from = 22, to = 22, protocol = "tcp", desc = "SSH" },
@@ -142,7 +152,7 @@ locals {
142152}
143153
144154provider "aws" {
145- region = local.aws_region
155+ region = local.aws_region
146156 profile = "myaws"
147157}
148158
@@ -182,9 +192,12 @@ resource "aws_instance" "ec2_instance" {
182192
183193 # prevent accidental termination of ec2 instance and data loss
184194 lifecycle {
185- # create_before_destroy = true #uncomment in production
195+ create_before_destroy = true #uncomment in production
186196 #prevent_destroy = true #uncomment in production
187197 ignore_changes = [ami]
198+ replace_triggered_by = [
199+ null_resource.docker_build_and_push
200+ ]
188201 }
189202
190203 root_block_device {
210223resource "null_resource" "wait_ssh" {
211224 depends_on = [aws_instance.ec2_instance]
212225
226+ triggers = (
227+ {
228+ instance_id = aws_instance.ec2_instance.id
229+ }
230+ )
231+
213232 provisioner "local-exec" {
214233 command = <<EOT
215234 bash -c '
@@ -227,9 +246,14 @@ resource "null_resource" "ansible_provision" {
227246 depends_on = [
228247 aws_instance.ec2_instance,
229248 local_file.ansible_inventory,
230- null_resource.wait_ssh
249+ null_resource.wait_ssh,
250+ local_file.image_tag
231251 ]
232252
253+ triggers = {
254+ instance_id = aws_instance.ec2_instance.id
255+ }
256+
233257 provisioner "local-exec" {
234258 interpreter = ["/bin/bash", "-c"]
235259
@@ -240,6 +264,7 @@ resource "null_resource" "ansible_provision" {
240264 EOT
241265 }
242266}
267+
243268```
244269
245270> 👆 Replace ` <your_app_name> ` with your app name (no spaces, only underscores or letters)
@@ -261,33 +286,43 @@ resource "aws_ecr_repository" "app_repo" {
261286data "aws_caller_identity" "current" {}
262287
263288resource "null_resource" "docker_build_and_push" {
264-
265289 depends_on = [aws_ecr_repository.app_repo]
266290
291+ triggers = {
292+ image_tag = local.image_tag
293+ }
294+
267295 provisioner "local-exec" {
268296 command = <<-EOT
269297 set -e
270298 unset DOCKER_HOST
271-
299+
272300 REPO_URL="${aws_ecr_repository.app_repo.repository_url}"
273301 ACCOUNT_ID="${data.aws_caller_identity.current.account_id}"
274302 REGION="${local.aws_region}"
275-
303+ TAG="${local.image_tag}"
304+
276305 echo "LOG: Logging in to ECR..."
277306 aws ecr get-login-password --region $${REGION} | docker login --username AWS --password-stdin $${ACCOUNT_ID}.dkr.ecr.$${REGION}.amazonaws.com
278307
279308 echo "LOG: Building Docker image..."
280- docker -H unix:///var/run/docker.sock build --pull -t $${REPO_URL}:latest ${local.app_source_code_path}
309+ docker build --pull -t $${REPO_URL}:$${TAG} ${local.app_source_code_path}
281310
282311 echo "LOG: Pushing image to ECR..."
283- docker -H unix:///var/run/docker.sock push $${REPO_URL}:latest
312+ docker push $${REPO_URL}:$${TAG}
284313
285- echo "LOG: Build and push complete."
314+ echo "LOG: Build and push complete. TAG=$${TAG} "
286315 EOT
287316
288317 interpreter = ["/bin/bash", "-c"]
289318 }
290319}
320+
321+ resource "local_file" "image_tag" {
322+ depends_on = [null_resource.docker_build_and_push]
323+ content = local.image_tag
324+ filename = "${path.module}/image_tag.txt"
325+ }
291326```
292327
293328This file contains a script that builds the Docker image locally. This is done for more flexible deployment. When changing the program code, there is no need to manually update the image on EC2 or in the repository. It is updated automatically with each terraform apply. Below is a table showing the time it takes to build this image from scratch and with minimal changes.
@@ -445,7 +480,7 @@ you need to create a file `Chart.yaml` in it
445480
446481``` yaml title="deploy/helm/helm_charts/Chart.yaml"
447482apiVersion : v2
448- name : myadmin
483+ name : myadmink3s # SET YOUR APP NAME
449484description : Helm chart for myadmin app
450485version : 0.1.0
451486appVersion : " 1.0.0"
@@ -454,11 +489,12 @@ appVersion: "1.0.0"
454489And ` values.yaml`
455490
456491` ` ` yaml title="deploy/helm/helm_charts/values.yaml"
457- appName: myadmin
492+ appName: myadmink3s # SET YOUR APP NAME LIKE IN Chart.yaml
493+ appNameSpace: myadmin # SET YOUR APP NAMESPACE
458494containerPort: 3500
459495servicePort: 80
460496adminSecret: "your_secret"
461- ecrImageFull: 735356255780.dkr.ecr.us-west-2.amazonaws.com/myadmink3s:latest ## <-- change to your repo url
497+ ecrImageFull: ""
462498` ` `
463499After this create .../deploy/helm/helm_charts/templates folder
464500
@@ -471,7 +507,7 @@ apiVersion: apps/v1
471507kind: Deployment
472508metadata:
473509 name: {{ .Values.appName }}-deployment
474- namespace: {{ .Values.appName }}
510+ namespace: {{ .Values.appNameSpace }}
475511spec:
476512 replicas: 1
477513 selector:
@@ -499,7 +535,7 @@ apiVersion: networking.k8s.io/v1
499535kind: Ingress
500536metadata:
501537 name: {{ .Values.appName }}-ingress
502- namespace: {{ .Values.appName }}
538+ namespace: {{ .Values.appNameSpace }}
503539spec:
504540 rules:
505541 - http:
@@ -520,7 +556,7 @@ apiVersion: v1
520556kind: Service
521557metadata:
522558 name: {{ .Values.appName }}-service
523- namespace: {{ .Values.appName }}
559+ namespace: {{ .Values.appNameSpace }}
524560spec:
525561 type: ClusterIP
526562 selector:
@@ -552,31 +588,38 @@ Then the file `playbook.yaml`
552588 kubeconfig_path: /etc/rancher/k3s/k3s.yaml
553589 helm_url: https://get.helm.sh/helm-v3.12.3-linux-amd64.tar.gz
554590 helm_dest: /usr/local/bin/helm
555- app_name: myadmin
556- app_namespace: myadmin
591+ app_name: myadmink3s # <-- CHANGE TO YOUR APP NAME LIKE IN main.tf local.app_name
592+ app_namespace: myadmin # <-- CHANGE TO YOUR APP NAMESPACE
593+ aws_account_id: "735356255780" # <-- CHANGE TO YOUR AWS ACCOUNT ID
557594 chart_path: /home/ubuntu/{{app_name}}/helm_charts
558595
559596 tasks:
560597
598+ - name: Read Docker image tag (local)
599+ ansible.builtin.set_fact:
600+ image_tag: "{{ lookup('file', '../../terraform/image_tag.txt') }}"
601+ delegate_to: localhost
602+
561603 - name: Install unzip
562- apt:
604+ ansible.builtin. apt:
563605 name: unzip
564606 state: present
565607 update_cache: true
566608
567609 - name: Download AWS CLI v2
568- get_url:
610+ ansible.builtin. get_url:
569611 url: "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"
570612 dest: /tmp/awscliv2.zip
613+ mode: '0644'
571614
572615 - name: Unzip AWS CLI
573- unarchive:
616+ ansible.builtin. unarchive:
574617 src: /tmp/awscliv2.zip
575618 dest: /tmp
576619 remote_src: true
577620
578621 - name: Install AWS CLI
579- command: /tmp/aws/install --update
622+ ansible.builtin. command: /tmp/aws/install --update
580623 args:
581624 creates: /usr/local/bin/aws
582625
@@ -594,25 +637,34 @@ Then the file `playbook.yaml`
594637 - ca-certificates
595638 state: present
596639
640+ - name: Download k3s installation script
641+ ansible.builtin.get_url:
642+ url: https://get.k3s.io
643+ dest: /tmp/install_k3s.sh
644+ mode: '0700'
645+
597646 - name: Install k3s
598- ansible.builtin.shell: |
599- curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION={{ k3s_version }} sh -
647+ ansible.builtin.command: /tmp/install_k3s.sh
648+ environment:
649+ INSTALL_K3S_VERSION: "{{ k3s_version }}"
600650 args:
601651 creates: /usr/local/bin/k3s
602652
603653 - name: Get ECR token
604- command: aws ecr get-login-password --region us-west-2
654+ ansible.builtin. command: aws ecr get-login-password --region us-west-2
605655 register: ecr_token
656+ changed_when: false
606657
607658 - name: Configure K3s registry for ECR
608- copy:
659+ ansible.builtin. copy:
609660 dest: /etc/rancher/k3s/registries.yaml
610661 content: |
611662 configs:
612663 "735356255780.dkr.ecr.us-west-2.amazonaws.com":
613664 auth:
614665 username: AWS
615666 password: "{{ ecr_token.stdout }}"
667+ mode: '0600'
616668
617669 - name: Restart k3s to apply registry changes
618670 ansible.builtin.systemd:
@@ -690,29 +742,31 @@ Then the file `playbook.yaml`
690742
691743 - name: Copy Helm chart to server
692744 ansible.builtin.copy:
693- src: "../../helm/helm_charts"
694- dest: /home/ubuntu/{{app_name}}
745+ src: "../../helm/helm_charts"
746+ dest: /home/ubuntu/{{ app_name }}
695747 owner: ubuntu
696748 group: ubuntu
697749 mode: '0755'
698750 force: true
699751
700- - name: Ensure {{ app_namespace}} namespace exists
752+ - name: Ensure namespace exists - {{ app_namespace }}
701753 kubernetes.core.k8s:
702754 api_version: v1
703755 kind: Namespace
704- name: myadmin
756+ name: "{{ app_namespace }}"
705757 kubeconfig: "{{ kubeconfig_path }}"
706758
707- - name: Deploy {{app_name}} stack via Helm
759+ - name: Deploy stack via Helm - {{ app_name }}
708760 kubernetes.core.helm:
709- name: myadmin
710- chart_ref: /home/ubuntu/{{app_name}}/helm_charts
711- release_namespace: "{{app_namespace}}"
761+ name: "{{ app_namespace }}"
762+ chart_ref: /home/ubuntu/{{ app_name }}/helm_charts
763+ release_namespace: "{{ app_namespace }}"
712764 kubeconfig: "{{ kubeconfig_path }}"
713765 create_namespace: false
714- values_files:
715- - /home/ubuntu/{{app_name}}/helm_charts/values.yaml
766+ values_files:
767+ - /home/ubuntu/{{ app_name }}/helm_charts/values.yaml
768+ values:
769+ ecrImageFull: "{{ aws_account_id }}.dkr.ecr.us-west-2.amazonaws.com/{{ app_name }}:{{ image_tag }}"
716770 force: true
717771 atomic: false
718772` ` `
0 commit comments