automated terminal push
This commit is contained in:
12
Dockerfile
Normal file
12
Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM python:3.10-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY app.py .
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
CMD ["gunicorn", "--bind", "0.0.0.0:8080", "app:app"]
|
114
Jenkinsfile
vendored
Normal file
114
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
pipeline {
|
||||
agent any
|
||||
environment {
|
||||
GITEA_REPO = 'https://code.jacquesingram.online/lenape/nvhi-atsila-microservice.git'
|
||||
GITEA_CREDS = '52ee0829-6e65-4951-925b-4186254c3f21'
|
||||
SONAR_HOST = 'https://sonar.jacquesingram.online'
|
||||
SONAR_TOKEN = credentials('sonar-token')
|
||||
ARTIFACTORY_URL = 'https://art.jacquesingram.online/artifactory/nvhi-atsila-docker'
|
||||
ARTIFACTORY_CREDS = 'artifactory-api'
|
||||
AWS_CRED_ID = 'aws-ci'
|
||||
AWS_REGION = 'us-east-2'
|
||||
TF_BACKEND_BUCKET = 'nvhi-atsila-tf-state'
|
||||
TF_BACKEND_PREFIX = 'ecs/terraform.tfstate'
|
||||
TF_DDB_TABLE = 'nvhi-atsila-locks'
|
||||
SSH_CRED_ID = 'jenkins-ssh'
|
||||
|
||||
TF_VAR_cluster_name = 'nvhi-atsila-cluster'
|
||||
TF_VAR_vpc_cidr = '10.0.0.0/16'
|
||||
TF_VAR_public_subnets = '10.0.1.0/24,10.0.2.0/24'
|
||||
TF_VAR_instance_type = 't2.micro'
|
||||
TF_VAR_key_pair_name = 'nvhi-atsila-deployer'
|
||||
// Injected from Jenkins Global Env
|
||||
TF_VAR_jenkins_ip_cidr = env.JENKINS_SSH_CIDR
|
||||
|
||||
IMAGE_NAME = 'lenape/nvhi-atsila-microservice'
|
||||
IMAGE_TAG = "v1.0.${env.BUILD_NUMBER}"
|
||||
}
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
git url: env.GITEA_REPO, credentialsId: env.GITEA_CREDS
|
||||
}
|
||||
}
|
||||
stage('SonarQube Scan') {
|
||||
steps {
|
||||
withSonarQubeEnv('SonarQube') {
|
||||
sh "sonar-scanner -Dsonar.projectKey=nvhi-atsila-microservice -Dsonar.sources=."
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Build & Push Docker Image') {
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry(env.ARTIFACTORY_URL, env.ARTIFACTORY_CREDS) {
|
||||
def img = docker.build("${env.IMAGE_NAME}:${env.IMAGE_TAG}")
|
||||
img.push()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Terraform Init & Apply') {
|
||||
steps {
|
||||
withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', credentialsId: env.AWS_CRED_ID]]) {
|
||||
dir('terraform') {
|
||||
sh """
|
||||
terraform init \
|
||||
-backend-config="bucket=${TF_BACKEND_BUCKET}" \
|
||||
-backend-config="key=${TF_BACKEND_PREFIX}" \
|
||||
-backend-config="region=${AWS_REGION}" \
|
||||
-backend-config="dynamodb_table=${TF_DDB_TABLE}"
|
||||
terraform apply -auto-approve \
|
||||
-var="cluster_name=${TF_VAR_cluster_name}" \
|
||||
-var="vpc_cidr=${TF_VAR_vpc_cidr}" \
|
||||
-var="public_subnets=${TF_VAR_public_subnets}" \
|
||||
-var="instance_type=${TF_VAR_instance_type}" \
|
||||
-var="key_pair_name=${TF_VAR_key_pair_name}" \
|
||||
-var="jenkins_ip_cidr=${TF_VAR_jenkins_ip_cidr}"
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Configure EC2 with Ansible') {
|
||||
steps {
|
||||
script {
|
||||
def ec2_ip = sh(
|
||||
script: "terraform -chdir=terraform output -raw ecs_instance_public_ip",
|
||||
returnStdout: true
|
||||
).trim()
|
||||
writeFile file: 'ansible/hosts', text: "[inventory_hosts]\n${ec2_ip} ansible_user=ubuntu"
|
||||
}
|
||||
ansiblePlaybook(
|
||||
playbook: 'ansible/configure_ecs.yml',
|
||||
inventory: 'ansible/hosts',
|
||||
credentialsId: env.SSH_CRED_ID
|
||||
)
|
||||
}
|
||||
}
|
||||
stage('Register & Deploy to ECS') {
|
||||
steps {
|
||||
withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', credentialsId: env.AWS_CRED_ID]]) {
|
||||
sh """
|
||||
aws ecs register-task-definition \
|
||||
--family ${TF_VAR_cluster_name} \
|
||||
--network-mode bridge \
|
||||
--container-definitions '[{
|
||||
"name":"health-workload",
|
||||
"image":"${env.ARTIFACTORY_URL}/${env.IMAGE_NAME}:${env.IMAGE_TAG}",
|
||||
"essential":true,
|
||||
"portMappings":[{"containerPort":8080,"hostPort":8080}]
|
||||
}]' \
|
||||
--region ${AWS_REGION}
|
||||
|
||||
aws ecs update-service \
|
||||
--cluster ${TF_VAR_cluster_name} \
|
||||
--service ${TF_VAR_cluster_name}-service \
|
||||
--force-new-deployment \
|
||||
--region ${AWS_REGION}
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
39
ansible/configure_ecs.yml
Normal file
39
ansible/configure_ecs.yml
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
- name: Configure EC2 for ECS Cluster
|
||||
hosts: inventory_hosts
|
||||
become: yes
|
||||
|
||||
vars:
|
||||
ecs_cluster_name: "nvhi-atsila-cluster"
|
||||
|
||||
tasks:
|
||||
- name: Install Docker
|
||||
apt:
|
||||
name: docker.io
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: Start and enable Docker
|
||||
service:
|
||||
name: docker
|
||||
state: started
|
||||
enabled: true
|
||||
|
||||
- name: Write ECS config file
|
||||
copy:
|
||||
dest: /etc/ecs/ecs.config
|
||||
content: |
|
||||
ECS_CLUSTER={{ ecs_cluster_name }}
|
||||
|
||||
- name: Run ECS agent container
|
||||
docker_container:
|
||||
name: ecs-agent
|
||||
image: amazon/amazon-ecs-agent:latest
|
||||
state: started
|
||||
restart_policy: always
|
||||
env_file: /etc/ecs/ecs.config
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /var/log/ecs:/log
|
||||
- /var/lib/ecs/data:/data
|
||||
network_mode: host
|
2
ansible/hosts
Normal file
2
ansible/hosts
Normal file
@@ -0,0 +1,2 @@
|
||||
[inventory_hosts]
|
||||
# overwritten dynamically by Jenkins with the EC2 public IP
|
10
app.py
Normal file
10
app.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from flask import Flask, jsonify
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/health')
|
||||
def health():
|
||||
return jsonify(status='OK')
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=8080)
|
1
lenape_key.pub
Normal file
1
lenape_key.pub
Normal file
@@ -0,0 +1 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDDFBAOogBj/GHKXQs6FLROGQfXkZe2uKbRron0We7ZOLgt6e1bI7U8IMe+DIH250CHSi4R5DBYFQF5Bk1TkS5cgMtPIAb87vRUGI3sLs29DQA/kllYiZlQi9ejxcEz2+TRWn10Q/Kltlb6ESNLnnnTsIUUxKUeY3MKFFd+V13FleSVLGYondwPWYwD/XJ6a3VwSTJ1wFKO+lpKknSjDl2ZOgYpWFALPH+EwMlRGVMrUXAB604zqR1XOzYXAAWnhmmC9IGgCzU/5JnEgFyhfZbR3kpEH8SmSXahvdFZERp+3j9d3ROjchqnf0Z0zZ7vzX+G+jvzT/jGOkzH9tx0/OqIO9f47OFF8iUfZgUtJU1QGbepdsmQqognhxfJQfMZbVtKUw7zt+mzJz3A0XcRp7IwVHaqJ2QW2dpXi4UbWtejtZqROg6byWq2FpvFGNIT3eiKTf+EpCoOec6YGSrRQlj73Ob0+FhmsyQ6e8KKncaRYx38PqtnWsI3UnLtdKmEJmDBPI0ipxJzmKJKtb0vtJPVYvFEpgiXSwnDX883rAUQrXR/EhOMmbMwk7JSes6/GXH9rWN10JHh1/i1LLpl+rg6VyktFgVBHzVw++y29QSfFixeTvFkkTS5kl//CpKd1GDQb9ZBH6SPgkgOjmASPUo+p5e/NiN/SIBSpYpMjOKs7Q== jacques@Xochiquetzal
|
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Flask==2.0.3
|
||||
gunicorn==20.1.0
|
8
terraform/backend.tf
Normal file
8
terraform/backend.tf
Normal file
@@ -0,0 +1,8 @@
|
||||
terraform {
|
||||
backend "s3" {
|
||||
bucket = "nvhi-atsila-tf-state"
|
||||
key = "ecs/terraform.tfstate"
|
||||
region = "us-east-2"
|
||||
dynamodb_table = "nvhi-atsila-locks"
|
||||
}
|
||||
}
|
139
terraform/main.tf
Normal file
139
terraform/main.tf
Normal file
@@ -0,0 +1,139 @@
|
||||
provider "aws" {
|
||||
region = var.aws_region
|
||||
}
|
||||
|
||||
data "aws_availability_zones" "azs" {}
|
||||
|
||||
# Hardened remote-state S3 bucket
|
||||
resource "aws_s3_bucket" "tfstate" {
|
||||
bucket = "nvhi-atsila-tf-state"
|
||||
|
||||
server_side_encryption_configuration {
|
||||
rule {
|
||||
apply_server_side_encryption_by_default {
|
||||
sse_algorithm = "AES256"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
versioning {
|
||||
enabled = true
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "nvhi-atsila-tf-state"
|
||||
Environment = "Production"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_public_access_block" "tfstate_block" {
|
||||
bucket = aws_s3_bucket.tfstate.id
|
||||
|
||||
block_public_acls = true
|
||||
block_public_policy = true
|
||||
ignore_public_acls = true
|
||||
restrict_public_buckets = true
|
||||
}
|
||||
|
||||
resource "aws_dynamodb_table" "locks" {
|
||||
name = "nvhi-atsila-locks"
|
||||
billing_mode = "PAY_PER_REQUEST"
|
||||
hash_key = "LockID"
|
||||
|
||||
attribute {
|
||||
name = "LockID"
|
||||
type = "S"
|
||||
}
|
||||
|
||||
point_in_time_recovery {
|
||||
enabled = true
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "nvhi-atsila-locks"
|
||||
Environment = "Production"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_vpc" "main" {
|
||||
cidr_block = var.vpc_cidr
|
||||
tags = {
|
||||
Name = "${var.cluster_name}-vpc"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_subnet" "public" {
|
||||
count = length(split(",", var.public_subnets))
|
||||
vpc_id = aws_vpc.main.id
|
||||
cidr_block = element(split(",", var.public_subnets), count.index)
|
||||
availability_zone = data.aws_availability_zones.azs.names[count.index]
|
||||
map_public_ip_on_launch = true
|
||||
tags = {
|
||||
Name = "${var.cluster_name}-public-${count.index}"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_security_group" "ecs_sg" {
|
||||
name = "${var.cluster_name}-sg"
|
||||
description = "Allow SSH & HTTP to ECS"
|
||||
vpc_id = aws_vpc.main.id
|
||||
|
||||
ingress {
|
||||
from_port = 22
|
||||
to_port = 22
|
||||
protocol = "tcp"
|
||||
cidr_blocks = [var.jenkins_ip_cidr]
|
||||
}
|
||||
|
||||
ingress {
|
||||
from_port = 8080
|
||||
to_port = 8080
|
||||
protocol = "tcp"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
egress {
|
||||
from_port = 0
|
||||
to_port = 0
|
||||
protocol = "-1"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "${var.cluster_name}-sg"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_key_pair" "deployer" {
|
||||
key_name = var.key_pair_name
|
||||
public_key = file("${path.module}/../lenape_key.pub")
|
||||
}
|
||||
|
||||
data "aws_ami" "ubuntu" {
|
||||
most_recent = true
|
||||
owners = ["099720109477"]
|
||||
filter {
|
||||
name = "name"
|
||||
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_ecs_cluster" "main" {
|
||||
name = var.cluster_name
|
||||
}
|
||||
|
||||
resource "aws_instance" "ecs_instance" {
|
||||
ami = data.aws_ami.ubuntu.id
|
||||
instance_type = var.instance_type
|
||||
subnet_id = aws_subnet.public[0].id
|
||||
vpc_security_group_ids = [aws_security_group.ecs_sg.id]
|
||||
key_name = aws_key_pair.deployer.key_name
|
||||
|
||||
tags = {
|
||||
Name = "${var.cluster_name}-instance"
|
||||
}
|
||||
}
|
||||
|
||||
output "ecs_instance_public_ip" {
|
||||
value = aws_instance.ecs_instance.public_ip
|
||||
}
|
34
terraform/variables.tf
Normal file
34
terraform/variables.tf
Normal file
@@ -0,0 +1,34 @@
|
||||
variable "aws_region" {
|
||||
type = string
|
||||
default = "us-east-2"
|
||||
}
|
||||
|
||||
variable "jenkins_ip_cidr" {
|
||||
description = "CIDR block for SSH access from Jenkins (injected by pipeline)"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "cluster_name" {
|
||||
description = "Name of the ECS cluster"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "vpc_cidr" {
|
||||
description = "VPC CIDR block"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "public_subnets" {
|
||||
description = "Comma-separated public subnet CIDRs"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "instance_type" {
|
||||
description = "EC2 instance type"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "key_pair_name" {
|
||||
description = "EC2 Key Pair name"
|
||||
type = string
|
||||
}
|
Reference in New Issue
Block a user