From 569ee1c9c7ef055caa07c3b70f3079e86189b0e7 Mon Sep 17 00:00:00 2001 From: Andrew Kang <46193964+patternhelloworld@users.noreply.github.com> Date: Fri, 8 Nov 2024 01:35:26 +0900 Subject: [PATCH 01/11] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fb389fe..4943bf6 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ Deploying web projects should be [simple, with high availability and security](h - **From Scratch** - Docker-Blue-Green-Runner's `run.sh` script is designed to simplify deployment: "With your `.env`, project, and a single Dockerfile, simply run 'bash run.sh'." This script covers the entire process from Dockerfile build to server deployment from scratch. + - This means you can easily migrate to another server with just the files mentioned above. - In contrast, Traefik requires the creation and gradual adjustment of various configuration files, which can introduce the types of errors mentioned above. - Focus on zero-downtime deployment on a single machine. @@ -593,4 +594,4 @@ git status # If any changes are detected, the source code may be corrupted. docker swarm init sudo bash run.sh ``` ---- \ No newline at end of file +--- From 5186b7b5e3cad7248b9087fb4c6e08c818a0b0e7 Mon Sep 17 00:00:00 2001 From: Andrew-Kang-G Date: Sat, 9 Nov 2024 01:08:27 +0900 Subject: [PATCH 02/11] feature : clarify messages and introduce CHECKPOINTs --- README.md | 77 +++++++++++++++++++------------------- documents/images/img5.png | Bin 0 -> 110741 bytes run.sh | 53 ++++++++++++++++++-------- use-app.sh | 11 +++++- use-common.sh | 49 ++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 55 deletions(-) create mode 100644 documents/images/img5.png diff --git a/README.md b/README.md index 4943bf6..b286602 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,11 @@ > One Simple Zero-Downtime Blue-Green Deployment with your Dockerfiles -Deploying web projects should be [simple, with high availability and security](https://github.com/Andrew-Kang-G/docker-blue-green-runner?tab=readme-ov-file#Quick-Guide-on-Usage). - - Use ``the latest Release version`` OR at least ``tagged versions`` for your production, NOT the latest commit of the 'main' branch. - In production, place your project in a separate folder, not in the samples folder, as they are just examples. ## Table of Contents +- [Process Summary](#process-summary) - [Features](#features) - [Requirements](#requirements) - [Quick Start with Samples](#quick-start-with-samples) @@ -34,7 +33,6 @@ Deploying web projects should be [simple, with high availability and security](h - [Consul](#consul) - [USE_NGINX_RESTRICTION on .env](#use_nginx_restriction-on-env) - [Advanced](#advanced) -- [Process Summary](#process-summary) - [Gitlab Container Registry](#gitlab-container-registry) - [Upload Image (CI/CD Server -> Git)](#upload-image-cicd-server---git) - [Download Image (Git -> Production Server)](#download-image-git---production-server) @@ -47,6 +45,44 @@ Deploying web projects should be [simple, with high availability and security](h --- +## Process Summary + +- Term Reference + - ``All`` means below is "App", "Nginx", "Consul&Registrator". + - ``(Re)Load`` means ``docker run.... OR docker-compose up``. + - ``State`` is ``Blue`` or ``Green`` + - More is on [Terms](#terms) +- Load Consul & Registrator, then the App, and finally Nginx to prevent upstream errors. + + +```mermaid +graph TD; + A[Initialize and Set Variables] --> B[Backup All Images] + B --> C[Check the .env File Integrity] + C --> D[Build All Images] + D --> E[Create Consul Network] + E --> F{Reload Consul if Required} + F -- Yes --> G[Reload Consul] + F -- No --> H[Load Your App] + G --> H[Load Your App] + H --> I[Check App Integrity] + I --> J{Reload Nginx if Required} + J -- Yes --> K[Check Nginx Template Integrity by Running a Test Container] + J -- No --> L[Check All Containers' Health] + K --> L[Check All Containers' Health] + L --> M{Set New State Using Consul Template} + M -- Fails --> O[Run Nginx Contingency Plan] + M -- Success --> N[External Integrity Check] + O --> N[External Integrity Check] + N -- Fails --> P[Rollback App if Needed] + N -- Success --> Q["Remove the Opposite State (Blue or Green) from the Running Containers"] + P --> Q["Remove the Opposite State from the Running Containers"] + Q --> R[Clean Up Dangling Images] + R --> S[Deployment Complete] + +``` +![img5.png](/documents/images/img5.png) + ## Features - **No Unpredictable Errors in Reverse Proxy and Deployment** @@ -492,43 +528,8 @@ bash check-source-integrity.sh - **For the properties of 'environment, volumes', use .env instead of setting them on the yml.** - Set ```USE_MY_OWN_APP_YML=true``` on .env - ```bash run.sh``` - -## Process Summary - -- Term Reference - - ``All`` means below is "App", "Nginx", "Consul&Registrator". - - ``(Re)Load`` means ``docker run.... OR docker-compose up``. - - ``State`` is ``Blue`` or ``Green`` - - More is on [Terms](#terms) -- Load Consul & Registrator, then the App, and finally Nginx to prevent upstream errors. - -```mermaid -graph TD; - A[Initialize and Set Variables] --> B[Backup All Images] - B --> C[Check the .env File Integrity] - C --> D[Build All Images] - D --> E[Create Consul Network] - E --> F{Reload Consul if Required} - F -- Yes --> G[Reload Consul] - F -- No --> H[Load Your App] - G --> H[Load Your App] - H --> I[Check App Integrity] - I --> J{Reload Nginx if Required} - J -- Yes --> K[Check Nginx Template Integrity by Running a Test Container] - J -- No --> L[Check All Containers' Health] - K --> L[Check All Containers' Health] - L --> M{Set New State Using Consul Template} - M -- Fails --> O[Run Nginx Contingency Plan] - M -- Success --> N[External Integrity Check] - O --> N[External Integrity Check] - N -- Fails --> P[Rollback App if Needed] - N -- Success --> Q["Remove the Opposite State (Blue or Green) from the Running Containers"] - P --> Q["Remove the Opposite State from the Running Containers"] - Q --> R[Clean Up Dangling Images] - R --> S[Deployment Complete] -``` ## Gitlab Container Registry ### Upload Image (CI/CD Server -> Git) diff --git a/documents/images/img5.png b/documents/images/img5.png new file mode 100644 index 0000000000000000000000000000000000000000..a47b17d526aa6ea5b953b50798607aee247c56fd GIT binary patch literal 110741 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 C7#Lvy literal 0 HcmV?d00001 diff --git a/run.sh b/run.sh index d1ada8f..6712f1e 100644 --- a/run.sh +++ b/run.sh @@ -4,6 +4,9 @@ set -eu source use-common.sh + +display_checkpoint_message "Checking versions for supporting libraries...(1%)" + check_bash_version check_gnu_grep_installed check_gnu_sed_installed @@ -18,7 +21,7 @@ sudo bash prevent-crlf.sh git config apply.whitespace nowarn git config core.filemode false -sleep 3 +sleep 1 source ./use-app.sh source ./use-nginx.sh @@ -181,16 +184,19 @@ backup_to_new_images(){ _main() { - # [A] Get mandatory variables + display_checkpoint_message "Initializing mandatory variables... (2%)" + cache_global_vars # The 'cache_all_states' in 'cache_global_vars' function decides which state should be deployed. If this is called later at a point in this script, states could differ. local initially_cached_old_state=${state} check_env_integrity - echo "[NOTICE] Finally, !! Deploy the App as !! ${new_state} !!, we will now deploy '${project_name}' in a way of 'Blue-Green'" + display_checkpoint_message "Deployment target between Blue and Green has been decided... (3%)" + display_planned_transition "$initially_cached_old_state" "$new_state" + sleep 2 - # [A-1] Set mandatory files ## App + display_checkpoint_message "Setting up the app configuration 'yml' for orchestration type: ${orchestration_type}... (5%)" initiate_docker_compose_file apply_env_service_name_onto_app_yaml apply_docker_compose_environment_onto_app_yaml @@ -200,8 +206,13 @@ _main() { if [[ ${skip_building_app_image} != 'true' ]]; then backup_app_to_previous_images fi + + ## Nginx if [[ ${nginx_restart} == 'true' ]]; then + + display_checkpoint_message "Since 'nginx_restart' is set to 'true', configuring the Nginx 'yml' for orchestration type: ${orchestration_type}... (7%)" + initiate_nginx_docker_compose_file apply_env_service_name_onto_nginx_yaml apply_ports_onto_nginx_yaml @@ -216,7 +227,9 @@ _main() { fi - # [A-2] Set 'Shared Volume Group' + display_checkpoint_message "Performing additional steps before building images... (10%)" + + # Set 'Shared Volume Group' # Detect the platform (Linux or Mac) if [[ "$(uname)" == "Darwin" ]]; then echo "[NOTICE] Running on Mac. Skipping 'add_host_users_to_host_group' as dscl is used for user and group management." @@ -227,7 +240,7 @@ _main() { fi fi - # [A-3] Etc. + # Etc. if [[ ${app_env} == 'local' ]]; then give_host_group_id_full_permissions fi @@ -235,19 +248,24 @@ _main() { terminate_whole_system fi - # [B] Build Docker images for the App, Nginx, Consul + + if [[ ${skip_building_app_image} != 'true' ]]; then - load_app_docker_image + display_checkpoint_message "Building Docker image for the app... ('skip_building_app_image' is set to false) (12%)" + load_app_docker_image fi - if [[ ${consul_restart} == 'true' ]]; then - load_consul_docker_image + display_checkpoint_message "Building Docker image for Consul... ('consul_restart' is set to true) (14%)" + load_consul_docker_image fi + if [[ ${nginx_restart} == 'true' ]]; then - load_nginx_docker_image + display_checkpoint_message "Building Docker image for Nginx... ('nginx_restart' is set to true) (16%)" + load_nginx_docker_image fi + if [[ ${only_building_app_image} == 'true' ]]; then echo "[NOTICE] Successfully built the App image : ${new_state}" && exit 0 fi @@ -259,17 +277,21 @@ _main() { (echo "[ERROR] Just checked all states shortly after the Docker Images had been done built. The state the App was supposed to be deployed as has been changed. (Original : ${cached_new_state}, New : ${new_state}). For the safety, we exit..." && exit 1) fi - # [C] docker-compose up the App, Nginx, Consul & * Internal Integrity Check for the App + # docker-compose up the App, Nginx, Consul & * Internal Integrity Check for the App + display_checkpoint_message "Starting docker-compose for App, Nginx, and Consul, followed by an internal integrity check for the app... (40%)" load_all_containers - # [D] Set Consul + + display_checkpoint_message "Reached the transition point... (65%)" + display_immediate_transition ${state} ${new_state} ./nginx-blue-green-activate.sh ${new_state} ${state} ${new_upstream} ${consul_key_value_store} # [E] External Integrity Check, if fails, 'emergency-nginx-down-and-up.sh' will be run. + display_checkpoint_message "Performing external integrity check. If it fails, 'emergency-nginx-down-and-up.sh' will be executed... (87%)" re=$(check_availability_out_of_container | tail -n 1); if [[ ${re} != 'true' ]]; then - echo "[WARNING] ! ${new_state}'s availability issue found. Now we are going to run 'emergency-nginx-down-and-up.sh' immediately." + display_checkpoint_message "[WARNING] ! ${new_state}'s availability issue found. Now we are going to run 'emergency-nginx-down-and-up.sh' immediately." bash emergency-nginx-down-and-up.sh re=$(check_availability_out_of_container | tail -n 1); @@ -281,6 +303,7 @@ _main() { # [F] Finalizing the process : from this point on, regarded as "success". + display_checkpoint_message "Finalizing the process. From this point, the deployment will be regarded as successful. (99%)" if [[ ${skip_building_app_image} != 'true' ]]; then backup_to_new_images fi @@ -304,7 +327,7 @@ _main() { echo "[NOTICE] Delete : images." docker rmi $(docker images -f "dangling=true" -q) || echo "[NOTICE] Any images in use will not be deleted." - echo "[NOTICE] APP_URL : ${app_url}" + display_checkpoint_message "[NOTICE] APP_URL : ${app_url}" } _main diff --git a/use-app.sh b/use-app.sh index 5a2510e..43c5465 100644 --- a/use-app.sh +++ b/use-app.sh @@ -272,7 +272,7 @@ check_availability_inside_container(){ for (( retry_count = 1; retry_count <= ${total_cnt}; retry_count++ )) do - echo "[NOTICE] ${retry_count} round health check (curl -s -k ${app_https_protocol}://$(concat_safe_port localhost)/${app_health_check_path})... (timeout : ${3} sec)" >&2 + echo -e "\033[1;35m[NOTICE] ${retry_count} round health check (curl -s -k ${app_https_protocol}://$(concat_safe_port localhost)/${app_health_check_path})... (timeout: ${3} sec)\033[0m" >&2 response=$(docker exec ${container_name} sh -c "curl -s -k ${app_https_protocol}://$(concat_safe_port localhost)/${app_health_check_path} --connect-timeout ${3}") down_count=$(echo ${response} | grep -Ei ${bad_app_health_check_pattern} | wc -l) @@ -281,7 +281,14 @@ check_availability_inside_container(){ if [[ ${down_count} -ge 1 || ${up_count} -lt 1 ]] then - echo "[WARNING] Unable to determine the response of the health check or the status is not UP, or Check the REDIRECT_HTTPS_TO_HTTP param in .env (*Response : ${response}), (${container_name}, *Log (print max 25 lines) : $(docker logs --tail 25 ${container_name})" >&2 + echo -e "\033[1;35m[WARNING] Unable to determine the health check response, or the status is not UP. Please check the following:\033[0m + \033[1;35m1) Verify the REDIRECT_HTTPS_TO_HTTP parameter in .env\033[0m + \033[1;35m2) Confirm your app's database connection\033[0m + \033[1;35m3) Review your health check settings in .env.\033[0m + \033[1;35m*Response:\033[0m ${response} + \033[1;35m*Container:\033[0m ${container_name} + \033[1;35m*Logs (last 25 lines):\033[0m $(docker logs --tail 25 ${container_name})" >&2 + else echo "[NOTICE] Internal health check of the application succeeded. (*Response: ${response})" >&2 diff --git a/use-common.sh b/use-common.sh index 02fbcfd..c244c5b 100644 --- a/use-common.sh +++ b/use-common.sh @@ -4,6 +4,55 @@ set -eu source ./validator.sh source ./use-states.sh + +display_checkpoint_message() { + local message=$1 + printf "\033[1;34m[CHECKPOINT] %s\033[0m\n" "$message" # Display message in bold blue +} + +# Function to display a transition message between states in a Blue-Green deployment +display_planned_transition() { + local current_state=$1 + local target_state=$2 + + # Clear the screen and set text to bold blue + echo -e "\033[1;34m" # Bold blue text + + echo "─────────────────────────────" + echo " Current State (${current_state})" + echo "─────────────────────────────" + echo " |" + echo " >> Transition planned <<" + echo " v" + echo "─────────────────────────────" + echo " Target State (${target_state})" + echo "─────────────────────────────" + echo -e "\033[0m" # Reset text style +} + +display_immediate_transition() { + local current_state=$1 + local target_state=$2 + + # Display the state transition diagram with a bold blue message + echo -e "\033[1;34m" # Bold blue text + + echo "─────────────────────────────" + echo " Current State (${current_state})" + echo "─────────────────────────────" + echo " |" + echo " >> Immediate Transition <<" + echo " v" + echo "─────────────────────────────" + echo " Target State (${target_state})" + echo "─────────────────────────────" + echo -e "\033[0m" # Reset text style + echo -e "\033[1;32m" # Bold green text for emphasis + echo ">>> Transition to ${target_state} is now being executed <<<" + echo -e "\033[0m" # Reset text style +} + + to_lower() { echo "$1" | tr '[:upper:]' '[:lower:]' } From d3787d98295f6d7d451303e0bcba08ea770eafed Mon Sep 17 00:00:00 2001 From: Andrew-Kang-G Date: Sat, 9 Nov 2024 13:43:25 +0900 Subject: [PATCH 03/11] feature : introduce a new property DOCKER_COMPOSE_HOST_VOLUME_CHECK to check if the host folder or file exists --- .env.example.local | 1 + .env.example.real | 3 ++- .env.java.local | 1 + .env.java.real | 1 + .env.java.real.commercial.ssl.sample | 2 +- .env.node.local | 1 + .env.php.local | 2 +- .env.php.real | 1 + README.md | 5 +++-- documents/Deploy-React-Project-with-DBGR.md | 3 ++- use-app.sh | 21 +++++++++++++++++ use-common.sh | 5 +++-- use-nginx.sh | 25 ++++++++++++++++++++- 13 files changed, 62 insertions(+), 9 deletions(-) diff --git a/.env.example.local b/.env.example.local index 5f6d523..963ba65 100644 --- a/.env.example.local +++ b/.env.example.local @@ -53,6 +53,7 @@ GOOD_APP_HEALTH_CHECK_PATTERN=xxx DOCKER_COMPOSE_ENVIRONMENT={"XDEBUG_CONFIG":"idekey=IDE_DEBUG","PHP_IDE_CONFIG":"serverName=laravel-crud-boilerplate"} DOCKER_BUILD_ARGS={} DOCKER_COMPOSE_NGINX_SELECTIVE_VOLUMES=[] +DOCKER_COMPOSE_HOST_VOLUME_CHECK=false NGINX_CLIENT_MAX_BODY_SIZE=50M diff --git a/.env.example.real b/.env.example.real index 606ea01..4485309 100644 --- a/.env.example.real +++ b/.env.example.real @@ -58,7 +58,8 @@ DOCKER_BUILD_ARGS={} DOCKER_COMPOSE_REAL_SELECTIVE_VOLUMES=[] # This is added on docker-compose-${project_name}-nginx.yml DOCKER_COMPOSE_NGINX_SELECTIVE_VOLUMES=["./shared/nginx-error-logs:/var/log/nginx"] - +# Check if the host folder or file exists +DOCKER_COMPOSE_HOST_VOLUME_CHECK=false NGINX_CLIENT_MAX_BODY_SIZE=50M diff --git a/.env.java.local b/.env.java.local index 00a48f4..5d7f2a2 100644 --- a/.env.java.local +++ b/.env.java.local @@ -48,6 +48,7 @@ DOCKER_COMPOSE_ENVIRONMENT={"TZ":"Asia/Seoul"} # docker exec -it CONTAINER_NAME cat /var/log/env_build_args.log DOCKER_BUILD_ARGS={"DOCKER_BUILDKIT":"1","PROJECT_ROOT_IN_CONTAINER":"/var/www/server/spring-sample-h-auth","APP_ENV":"local"} DOCKER_COMPOSE_NGINX_SELECTIVE_VOLUMES=[] +DOCKER_COMPOSE_HOST_VOLUME_CHECK=false NGINX_CLIENT_MAX_BODY_SIZE=50M diff --git a/.env.java.real b/.env.java.real index 01ebaf5..9e44386 100644 --- a/.env.java.real +++ b/.env.java.real @@ -50,6 +50,7 @@ DOCKER_COMPOSE_ENVIRONMENT={"TZ":"Asia/Seoul"} DOCKER_BUILD_ARGS={"DOCKER_BUILDKIT":"1","PROJECT_ROOT_IN_CONTAINER":"/var/www/server/spring-sample-h-auth","APP_ENV":"production"} DOCKER_COMPOSE_REAL_SELECTIVE_VOLUMES=["./samples/spring-sample-h-auth/logs:/var/www/files"] DOCKER_COMPOSE_NGINX_SELECTIVE_VOLUMES=["./shared/nginx-error-logs:/var/log/nginx"] +DOCKER_COMPOSE_HOST_VOLUME_CHECK=false NGINX_CLIENT_MAX_BODY_SIZE=50M diff --git a/.env.java.real.commercial.ssl.sample b/.env.java.real.commercial.ssl.sample index b6e9d1a..ed83a89 100644 --- a/.env.java.real.commercial.ssl.sample +++ b/.env.java.real.commercial.ssl.sample @@ -51,7 +51,7 @@ DOCKER_BUILD_ARGS={"DOCKER_BUILDKIT":"1","PROJECT_ROOT_IN_CONTAINER":"/var/www/s # 3) ''/var/web/project/spring-sample-h-auth' is here as well. The thing is you should locate 'application.properties', 'logback-spring.xml', 'yourdomain.com.jks' on the './src/main/resource' folder. DOCKER_COMPOSE_REAL_SELECTIVE_VOLUMES=["/var/web/files/spring-sample-h-auth:/var/www/files","/var/web/project/spring-sample-h-auth/src/main/resources:/var/www/server/spring-sample-h-auth/src/main/resources"] DOCKER_COMPOSE_NGINX_SELECTIVE_VOLUMES=["/var/web/files/nginx/spring-sample-h-auth/logs:/var/log/nginx"] - +DOCKER_COMPOSE_HOST_VOLUME_CHECK=false NGINX_CLIENT_MAX_BODY_SIZE=50M diff --git a/.env.node.local b/.env.node.local index aebab9b..1333c86 100644 --- a/.env.node.local +++ b/.env.node.local @@ -45,6 +45,7 @@ GOOD_APP_HEALTH_CHECK_PATTERN=docs DOCKER_COMPOSE_ENVIRONMENT={"MONGODB_URL":"mongodb://host.docker.internal:27017/node-boilerplate","NODE_ENV":"development"} DOCKER_BUILD_ARGS={} DOCKER_COMPOSE_NGINX_SELECTIVE_VOLUMES=[] +DOCKER_COMPOSE_HOST_VOLUME_CHECK=false NGINX_CLIENT_MAX_BODY_SIZE=50M diff --git a/.env.php.local b/.env.php.local index 52a5fe4..8494a7c 100644 --- a/.env.php.local +++ b/.env.php.local @@ -49,7 +49,7 @@ DOCKER_COMPOSE_ENVIRONMENT={"XDEBUG_CONFIG":"idekey=IDE_DEBUG","PHP_IDE_CONFIG": # docker exec -it CONTAINER_NAME cat /var/log/env_build_args.log DOCKER_BUILD_ARGS={"SAMPLE":"YAHOO","SAMPLE2":"YAHOO2"} DOCKER_COMPOSE_NGINX_SELECTIVE_VOLUMES=[] - +DOCKER_COMPOSE_HOST_VOLUME_CHECK=false NGINX_CLIENT_MAX_BODY_SIZE=50M diff --git a/.env.php.real b/.env.php.real index c36b866..2a394c5 100644 --- a/.env.php.real +++ b/.env.php.real @@ -52,6 +52,7 @@ DOCKER_BUILD_ARGS={"SAMPLE":"YAHOO","SAMPLE2":"YAHOO2","shared_volume_group_id": # For SSL, the host folder is recommended to be './.docker/ssl' to be synchronized with 'docker-compose-nginx-original.yml' DOCKER_COMPOSE_REAL_SELECTIVE_VOLUMES=["./shared/app-error-logs:/var/www/app/storage/logs","./.docker/ssl:/etc/apache2/ssl"] DOCKER_COMPOSE_NGINX_SELECTIVE_VOLUMES=["./shared/nginx-error-logs:/var/log/nginx"] +DOCKER_COMPOSE_HOST_VOLUME_CHECK=false NGINX_CLIENT_MAX_BODY_SIZE=50M diff --git a/README.md b/README.md index b286602..43de3c0 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ graph TD; | yq | 4.35.1 | Manual | Use v4.35.1 instead of the latest version. The lastest version causes a parsing error | | consul (docker image) | 1.14.11 | Auto | An error occurred due to a payload format issue while the lastest version of it was communicating with gliderlabs/registrator. | | gliderlabs/registrator (docker image) | master | Auto | | -| nginx (docker image) | latest | Auto | Considering changing it to a certain version, but until now no issues have been detected. | +| nginx (docker image) | 1.25.4 | Auto | Considering changing it to a certain version, but until now no issues have been detected. | | docker | 24~27 | Manual | I think too old versions could cause problems, and the lastest version v27.x causes only a warning message. | | docker-compose | 2 | Manual | I think too old versions could cause problems, and the v2 is recommended. | @@ -327,7 +327,8 @@ APP_ENV=real # The 'real' setting requires defining 'DOCKER_COMPOSE_REAL_SELECTIVE_VOLUMES'. DOCKER_COMPOSE_REAL_SELECTIVE_VOLUMES=["/my-host/files/:/in-container/files", "/my-host/java-spring-project/src/main/resources:/in-container/java-spring-project/src/main/resources"] - +# Check if the host folder or file exists +DOCKER_COMPOSE_HOST_VOLUME_CHECK=false # If APP_ENV is set to 'local', as specified in 'docker-compose-app-local-original.yml', synchronize your entire project as follows: "HOST_ROOT_LOCATION:PROJECT_LOCATION". # [IMPORTANT] If this is set to true, Nginx will be restarted, resulting in a short downtime. # This option should be used when upgrading the Runner. See the "Upgrade" section below. diff --git a/documents/Deploy-React-Project-with-DBGR.md b/documents/Deploy-React-Project-with-DBGR.md index 3c8a967..15c4db4 100644 --- a/documents/Deploy-React-Project-with-DBGR.md +++ b/documents/Deploy-React-Project-with-DBGR.md @@ -76,7 +76,8 @@ DOCKER_COMPOSE_REAL_SELECTIVE_VOLUMES=["/var/projects/your-app/.docker/nginx/app.conf.ctmpl:/etc/nginx-template/app.conf.ctmpl","/var/projects/files/your-app/logs:/var/log/nginx"] # [IMPORTANT] Run mkdir -p /var/projects/files/nginx/logs on your host machine DOCKER_COMPOSE_NGINX_SELECTIVE_VOLUMES=["/var/projects/files/nginx/logs:/var/log/nginx"] - + DOCKER_COMPOSE_HOST_VOLUME_CHECK=false + NGINX_CLIENT_MAX_BODY_SIZE=50M USE_MY_OWN_APP_YML=false diff --git a/use-app.sh b/use-app.sh index 43c5465..d0fbb8a 100644 --- a/use-app.sh +++ b/use-app.sh @@ -72,10 +72,31 @@ apply_docker_compose_environment_onto_app_yaml(){ } +check_docker_compose_real_host_volumes_directories() { + + local volumes=$(echo "${docker_compose_real_selective_volumes[@]}" | tr -d '[]"') + + for volume in ${volumes} + do + # Extract the local directory path before the colon (:) + local_dir="${volume%%:*}" + + # Check if the directory or file exists + if [[ ! -f "$local_dir" && ! -d "$local_dir" ]]; then + echo "[ERROR] The local path '$local_dir' specified in DOCKER_COMPOSE_REAL_SELECTIVE_VOLUMES does not exist. Exiting..." + exit 1 + fi + done +} + apply_docker_compose_volumes_onto_app_real_yaml(){ check_yq_installed + if [[ ${docker_compose_host_volume_check} == 'true' ]]; then + check_docker_compose_real_host_volumes_directories + fi + echo "[NOTICE] DOCKER_COMPOSE_REAL_SELECTIVE_VOLUMES on .env is now being applied to docker-${orchestration_type}-${project_name}-real.yml." if [[ ${orchestration_type} == 'stack' ]]; then diff --git a/use-common.sh b/use-common.sh index c244c5b..2002127 100644 --- a/use-common.sh +++ b/use-common.sh @@ -22,7 +22,7 @@ display_planned_transition() { echo " Current State (${current_state})" echo "─────────────────────────────" echo " |" - echo " >> Transition planned <<" + echo " >> Transition planned <<" echo " v" echo "─────────────────────────────" echo " Target State (${target_state})" @@ -41,7 +41,7 @@ display_immediate_transition() { echo " Current State (${current_state})" echo "─────────────────────────────" echo " |" - echo " >> Immediate Transition <<" + echo " >> Immediate Transition <<" echo " v" echo "─────────────────────────────" echo " Target State (${target_state})" @@ -135,6 +135,7 @@ cache_non_dependent_global_vars() { fi docker_compose_nginx_selective_volumes=$(get_value_from_env "DOCKER_COMPOSE_NGINX_SELECTIVE_VOLUMES") + docker_compose_host_volume_check=$(get_value_from_env "DOCKER_COMPOSE_HOST_VOLUME_CHECK") docker_layer_corruption_recovery=$(get_value_from_env "DOCKER_LAYER_CORRUPTION_RECOVERY") diff --git a/use-nginx.sh b/use-nginx.sh index c4b44c3..901559d 100644 --- a/use-nginx.sh +++ b/use-nginx.sh @@ -26,10 +26,33 @@ apply_ports_onto_nginx_yaml(){ done } + +check_docker_compose_nginx_host_volumes_directories() { + + local volumes=$(echo "${docker_compose_nginx_selective_volumes[@]}" | tr -d '[]"') + + for volume in ${volumes} + do + # Extract the local directory path before the colon (:) + local_dir="${volume%%:*}" + + # Check if the directory or file exists + if [[ ! -f "$local_dir" && ! -d "$local_dir" ]]; then + echo "[ERROR] The local path '$local_dir' specified in DOCKER_COMPOSE_NGINX_SELECTIVE_VOLUMES does not exist. Exiting..." + exit 1 + fi + done +} + + apply_docker_compose_volumes_onto_app_nginx_yaml(){ check_yq_installed + if [[ ${docker_compose_host_volume_check} == 'true' ]]; then + check_docker_compose_nginx_host_volumes_directories + fi + echo "[NOTICE] DOCKER_COMPOSE_NGINX_SELECTIVE_VOLUMES on .env is now being applied to docker-compose-${project_name}-nginx.yml." for volume in "${docker_compose_nginx_selective_volumes[@]}" @@ -293,7 +316,7 @@ nginx_down(){ nginx_up(){ echo "[NOTICE] Up NGINX Container." - PROJECT_NAME=${project_name} docker-compose -f docker-compose-${project_name}-nginx.yml up -d || echo "[ERROR] Critical - ${project_name}-nginx UP failure" + PROJECT_NAME=${project_name} docker-compose -f docker-compose-${project_name}-nginx.yml up -d || echo "[ERROR] Critical - ${project_name}-nginx UP failure." } From 87a369a3eae85db5dac6351afc9c5a8cc6ad7367 Mon Sep 17 00:00:00 2001 From: Andrew-Kang-G Date: Sat, 9 Nov 2024 14:00:04 +0900 Subject: [PATCH 04/11] doc : add explanations on HEALTH_CHECK --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 43de3c0..e342436 100644 --- a/README.md +++ b/README.md @@ -325,6 +325,19 @@ sudo bash run.sh # Set this to 'real' in the .env file for production environments. APP_ENV=real +# This path is used for both internal and external health checks. +# Note: Do not include a leading slash ("/") at the start of the path. +# Example: "api/v1/health" (correct), "/api/v1/health" (incorrect) +APP_HEALTH_CHECK_PATH=api/v1/health + +# The BAD & GOOD conditions are checked using an "AND" condition. +# To ignore the "BAD_APP_HEALTH_CHECK_PATTERN", set it to a non-existing value (e.g., "###lsdladc"). +BAD_APP_HEALTH_CHECK_PATTERN=DOWN + +# Pattern required for a successful health check. +GOOD_APP_HEALTH_CHECK_PATTERN=UP + + # The 'real' setting requires defining 'DOCKER_COMPOSE_REAL_SELECTIVE_VOLUMES'. DOCKER_COMPOSE_REAL_SELECTIVE_VOLUMES=["/my-host/files/:/in-container/files", "/my-host/java-spring-project/src/main/resources:/in-container/java-spring-project/src/main/resources"] # Check if the host folder or file exists From 3231178a12159cbfec39c3626d372388aa588c4a Mon Sep 17 00:00:00 2001 From: Andrew-Kang-G Date: Sat, 9 Nov 2024 14:31:16 +0900 Subject: [PATCH 05/11] feature : yq install (manual -> auto) --- .gitignore | 4 +++ bin/.gitkeep | 0 use-app.sh | 26 +++++++++---------- use-common.sh | 70 ++++++++++++++++++++++++++++++++++++++++----------- use-nginx.sh | 10 ++++---- 5 files changed, 78 insertions(+), 32 deletions(-) create mode 100644 bin/.gitkeep diff --git a/.gitignore b/.gitignore index 9580455..b707aa2 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,10 @@ /shared/system-logs/* !/shared/system-logs/.gitkeep +/bin/* +!/bin/.gitkeep + + **/.idea /.env /.env.ready.* diff --git a/bin/.gitkeep b/bin/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/use-app.sh b/use-app.sh index d0fbb8a..245337c 100644 --- a/use-app.sh +++ b/use-app.sh @@ -35,14 +35,14 @@ apply_env_service_name_onto_app_yaml(){ check_yq_installed if [[ ${orchestration_type} == 'stack' ]]; then - yq -i "with(.services; with_entries(select(.key ==\"*-${new_state}\") | .key |= \"${project_name}-${new_state}\"))" docker-${orchestration_type}-${project_name}-${app_env}.yml || (echo "[ERROR] Failed to apply the green service name in the app YAML as ${project_name}." && exit 1) - # yq eval '(.services.[] | select(.image == "${PROJECT_NAME}:blue")).image |= \"${project_name}-blue\"' -i docker-${orchestration_type}-${project_name}-blue.yml || (echo "[ERROR] Failed to apply image : ${project_name}-blue in the app YAML." && exit 1) - yq -i "(.services.\"${project_name}-${new_state}\").image = \"${project_name}:${new_state}\"" -i docker-${orchestration_type}-${project_name}-${app_env}.yml || (echo "[ERROR] Failed to apply image : ${project_name}-${new_state} in the app YAML." && exit 1) + bin/yq -i "with(.services; with_entries(select(.key ==\"*-${new_state}\") | .key |= \"${project_name}-${new_state}\"))" docker-${orchestration_type}-${project_name}-${app_env}.yml || (echo "[ERROR] Failed to apply the green service name in the app YAML as ${project_name}." && exit 1) + # bin/yq eval '(.services.[] | select(.image == "${PROJECT_NAME}:blue")).image |= \"${project_name}-blue\"' -i docker-${orchestration_type}-${project_name}-blue.yml || (echo "[ERROR] Failed to apply image : ${project_name}-blue in the app YAML." && exit 1) + bin/yq -i "(.services.\"${project_name}-${new_state}\").image = \"${project_name}:${new_state}\"" -i docker-${orchestration_type}-${project_name}-${app_env}.yml || (echo "[ERROR] Failed to apply image : ${project_name}-${new_state} in the app YAML." && exit 1) else echo "[NOTICE] PROJECT_NAME on .env is now being applied to docker-${orchestration_type}-${project_name}-${app_env}.yml." - yq -i "with(.services; with_entries(select(.key ==\"*-blue\") | .key |= \"${project_name}-blue\"))" docker-${orchestration_type}-${project_name}-${app_env}.yml || (echo "[ERROR] Failed to apply the blue service name in the app YAML as ${project_name}." && exit 1) + bin/yq -i "with(.services; with_entries(select(.key ==\"*-blue\") | .key |= \"${project_name}-blue\"))" docker-${orchestration_type}-${project_name}-${app_env}.yml || (echo "[ERROR] Failed to apply the blue service name in the app YAML as ${project_name}." && exit 1) sleep 2 - yq -i "with(.services; with_entries(select(.key ==\"*-green\") | .key |= \"${project_name}-green\"))" docker-${orchestration_type}-${project_name}-${app_env}.yml || (echo "[ERROR] Failed to apply the green service name in the app YAML as ${project_name}." && exit 1) + bin/yq -i "with(.services; with_entries(select(.key ==\"*-green\") | .key |= \"${project_name}-green\"))" docker-${orchestration_type}-${project_name}-${app_env}.yml || (echo "[ERROR] Failed to apply the green service name in the app YAML as ${project_name}." && exit 1) fi } @@ -61,12 +61,12 @@ apply_docker_compose_environment_onto_app_yaml(){ for state in "${states[@]}" do - yq -i '.services.'${project_name}'-'${state}'.environment = []' docker-${orchestration_type}-${project_name}-${app_env}.yml - yq -i '.services.'${project_name}'-'${state}'.environment += "SERVICE_NAME='${state}'"' docker-${orchestration_type}-${project_name}-${app_env}.yml + bin/yq -i '.services.'${project_name}'-'${state}'.environment = []' docker-${orchestration_type}-${project_name}-${app_env}.yml + bin/yq -i '.services.'${project_name}'-'${state}'.environment += "SERVICE_NAME='${state}'"' docker-${orchestration_type}-${project_name}-${app_env}.yml - for ((i=1; i<=$(echo ${docker_compose_environment} | yq eval 'length'); i++)) + for ((i=1; i<=$(echo ${docker_compose_environment} | bin/yq eval 'length'); i++)) do - yq -i '.services.'${project_name}'-'${state}'.environment += "'$(echo ${docker_compose_environment} | yq -r 'to_entries | .['$((i-1))'].key')'='$(echo ${docker_compose_environment} | yq -r 'to_entries | .['$((i-1))'].value')'"' docker-${orchestration_type}-${project_name}-${app_env}.yml + bin/yq -i '.services.'${project_name}'-'${state}'.environment += "'$(echo ${docker_compose_environment} | bin/yq -r 'to_entries | .['$((i-1))'].key')'='$(echo ${docker_compose_environment} | bin/yq -r 'to_entries | .['$((i-1))'].value')'"' docker-${orchestration_type}-${project_name}-${app_env}.yml done done @@ -107,11 +107,11 @@ apply_docker_compose_volumes_onto_app_real_yaml(){ for state in "${states[@]}" do - #yq -i '.services.'${project_name}'-'${state}'.volumes = []' ./docker-${orchestration_type}-${project_name}-real.yml + #bin/yq -i '.services.'${project_name}'-'${state}'.volumes = []' ./docker-${orchestration_type}-${project_name}-real.yml for volume in "${docker_compose_real_selective_volumes[@]}" do - yq -i '.services.'${project_name}'-'${state}'.volumes += '${volume}'' ./docker-${orchestration_type}-${project_name}-real.yml + bin/yq -i '.services.'${project_name}'-'${state}'.volumes += '${volume}'' ./docker-${orchestration_type}-${project_name}-real.yml done done @@ -126,9 +126,9 @@ make_docker_build_arg_strings(){ local re="" - for ((i=1; i<=$(echo ${docker_build_args} | yq eval 'length'); i++)) + for ((i=1; i<=$(echo ${docker_build_args} | bin/yq eval 'length'); i++)) do - re="${re} --build-arg $(echo ${docker_build_args} | yq -r 'to_entries | .['$((i-1))'].key')=$(echo ${docker_build_args} | yq -r 'to_entries | .['$((i-1))'].value')" + re="${re} --build-arg $(echo ${docker_build_args} | bin/yq -r 'to_entries | .['$((i-1))'].key')=$(echo ${docker_build_args} | bin/yq -r 'to_entries | .['$((i-1))'].value')" done echo ${re} diff --git a/use-common.sh b/use-common.sh index 2002127..7547517 100644 --- a/use-common.sh +++ b/use-common.sh @@ -65,8 +65,8 @@ set_expose_and_app_port(){ fi if echo "${1}" | grep -Eq '^\[[0-9]+,[0-9]+\]$'; then - expose_port=$(echo "$project_port" | yq e '.[0]' -) - app_port=$(echo "$project_port" | yq e '.[1]' -) + expose_port=$(echo "$project_port" | bin/yq e '.[0]' -) + app_port=$(echo "$project_port" | bin/yq e '.[1]' -) else expose_port="$project_port" app_port="$project_port" @@ -255,25 +255,67 @@ cache_global_vars() { check_yq_installed(){ required_version="4.35.1" + yq_path="bin/yq" + + # Function to download yq + download_yq() { + echo "[NOTICE] Downloading bin/yq version $required_version..." >&2 + + # Detect OS and architecture + ARCH=$(uname -m) + OS=$(uname | tr '[:upper:]' '[:lower:]') + + # Determine the correct bin/yq binary based on architecture + case "$OS-$ARCH" in + linux-x86_64) + YQ_BINARY="yq_linux_amd64" + ;; + linux-aarch64) + YQ_BINARY="yq_linux_arm64" + ;; + linux-armv7l | linux-armhf) + YQ_BINARY="yq_linux_arm" + ;; + linux-i386 | linux-i686) + YQ_BINARY="yq_linux_386" + ;; + darwin-x86_64) + YQ_BINARY="yq_darwin_amd64" + ;; + darwin-arm64) + YQ_BINARY="yq_darwin_arm64" + ;; + *) + echo >&2 "[ERROR] Unsupported OS or architecture: $OS-$ARCH" + exit 1 + ;; + esac + + DOWNLOAD_URL="https://github.com/mikefarah/yq/releases/download/v$required_version/$YQ_BINARY" + + # Download yq + curl -L "$DOWNLOAD_URL" -o "$yq_path" + chmod +x "$yq_path" + echo "[NOTICE] bin/yq version $required_version from $DOWNLOAD_URL has been downloaded to $yq_path." >&2 + } - # Check if yq is installed - if ! command -v yq >/dev/null 2>&1; then - echo >&2 "[ERROR] yq is NOT installed. Please install yq version $required_version manually." - echo >&2 "You can download it from the following URL:" - echo >&2 "https://github.com/mikefarah/yq/releases/download/v$required_version/yq_linux_amd64" - exit 1 + # Check if bin/yq is installed in the bin directory + if [ ! -f "$yq_path" ]; then + echo "[WARNING] bin/yq is not found in $yq_path. Downloading..." >&2 + download_yq else - # Check if installed version is not 4.35.1 - installed_version=$(yq --version | grep -oP 'version v\K[0-9.]+') + # Check if installed bin/yq version is the required version + installed_version=$("$yq_path" --version | grep -oP 'version v\K[0-9.]+') if [ "$installed_version" != "$required_version" ]; then - echo >&2 "[ERROR] yq version is $installed_version. Please install yq version $required_version manually." - echo >&2 "You can download it from the following URL:" - echo >&2 "https://github.com/mikefarah/yq/releases/download/v$required_version/yq_linux_amd64" - exit 1 + echo "[WARNING] bin/yq version is $installed_version, which is not the required version $required_version." >&2 + download_yq + else + echo "[NOTICE] bin/yq version $required_version is already installed in $yq_path." >&2 fi fi } + check_gnu_grep_installed() { # Check if grep is installed if ! command -v grep >/dev/null 2>&1; then diff --git a/use-nginx.sh b/use-nginx.sh index 901559d..e2830a0 100644 --- a/use-nginx.sh +++ b/use-nginx.sh @@ -9,20 +9,20 @@ initiate_nginx_docker_compose_file(){ echo "[DEBUG] successfully copied docker-compose-app-nginx-original.yml" } apply_env_service_name_onto_nginx_yaml(){ - yq -i "with(.services; with_entries(select(.key ==\"*-nginx\") | .key |= \"${project_name}-nginx\"))" docker-compose-${project_name}-nginx.yml || (echo "[ERROR] Failed to apply the service name in the Nginx YML as ${project_name}." && exit 1) + bin/yq -i "with(.services; with_entries(select(.key ==\"*-nginx\") | .key |= \"${project_name}-nginx\"))" docker-compose-${project_name}-nginx.yml || (echo "[ERROR] Failed to apply the service name in the Nginx YML as ${project_name}." && exit 1) } apply_ports_onto_nginx_yaml(){ check_yq_installed echo "[NOTICE] PORTS on .env is now being applied to docker-compose-${project_name}-nginx.yml." - yq -i '.services.'${project_name}'-nginx.ports = []' docker-compose-${project_name}-nginx.yml - yq -i '.services.'${project_name}'-nginx.ports += "'${expose_port}':'${expose_port}'"' docker-compose-${project_name}-nginx.yml + bin/yq -i '.services.'${project_name}'-nginx.ports = []' docker-compose-${project_name}-nginx.yml + bin/yq -i '.services.'${project_name}'-nginx.ports += "'${expose_port}':'${expose_port}'"' docker-compose-${project_name}-nginx.yml for i in "${additional_ports[@]}" do [ -z "${i##*[!0-9]*}" ] && (echo "[ERROR] Wrong port number on .env : ${i}" && exit 1); - yq -i '.services.'${project_name}'-nginx.ports += "'$i:$i'"' docker-compose-${project_name}-nginx.yml + bin/yq -i '.services.'${project_name}'-nginx.ports += "'$i:$i'"' docker-compose-${project_name}-nginx.yml done } @@ -57,7 +57,7 @@ apply_docker_compose_volumes_onto_app_nginx_yaml(){ for volume in "${docker_compose_nginx_selective_volumes[@]}" do - yq -i '.services.'${project_name}'-'nginx'.volumes += '${volume}'' ./docker-compose-${project_name}-nginx.yml + bin/yq -i '.services.'${project_name}'-'nginx'.volumes += '${volume}'' ./docker-compose-${project_name}-nginx.yml done } From 2ecaa2a53e91b048072b740e288f8813d227aa45 Mon Sep 17 00:00:00 2001 From: Andrew-Kang-G Date: Sat, 9 Nov 2024 14:45:21 +0900 Subject: [PATCH 06/11] fix : permissions for Darwin --- README.md | 2 +- apply-security.sh | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e342436..8c97ad8 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ graph TD; | git | N/A | Manual | - | | bash | 4.4 at least | Manual | - | | curl | N/A | Manual | - | -| yq | 4.35.1 | Manual | Use v4.35.1 instead of the latest version. The lastest version causes a parsing error | +| yq | 4.35.1 | Auto | Use v4.35.1 instead of the latest version. The lastest version causes a parsing error | | consul (docker image) | 1.14.11 | Auto | An error occurred due to a payload format issue while the lastest version of it was communicating with gliderlabs/registrator. | | gliderlabs/registrator (docker image) | master | Auto | | | nginx (docker image) | 1.25.4 | Auto | Considering changing it to a certain version, but until now no issues have been detected. | diff --git a/apply-security.sh b/apply-security.sh index fa4f512..e779482 100644 --- a/apply-security.sh +++ b/apply-security.sh @@ -32,7 +32,21 @@ sudo chmod 750 *.sh || echo "[WARN] Running chmod 750 *.sh failed." sudo chmod 770 *.yml || echo "[WARN] Running chmod 770 *.yml failed." sudo chmod 740 .env.* || echo "[WARN] Running chmod 740 .env.* failed." sudo chmod 740 .env || echo "[WARN] Running chmod 740 .env failed." +sudo chmod -R 750 bin || echo "[WARN] Running chmod 750 for the bin folder" sudo chmod 770 .gitignore || echo "[WARN] Running chmod 770 .gitignore failed." sudo chmod -R 770 .docker/ || echo "[WARN] Running chmod -R 770 .docker/ failed." -sudo chown -R 0:${shared_volume_group_id} .docker/ || echo "[WARN] Running chgrp ${shared_volume_group_id} .docker/ failed." +# Check if the OS is not Darwin (macOS) before running the command +if [[ "$(uname)" != "Darwin" ]]; then + sudo chown -R 0:${shared_volume_group_id} .docker/ || echo "[WARN] Running chgrp ${shared_volume_group_id} .docker/ failed." +else + echo "[NOTICE] Skipping chown command on Darwin (macOS) platform. See the README." +fi + +if [[ "$(uname)" != "Darwin" ]]; then + sudo chown -R 0:${shared_volume_group_id} bin/ || echo "[WARN] Running chgrp ${shared_volume_group_id} bin/ failed." +else + echo "[NOTICE] Skipping chown command on Darwin (macOS) platform. See the README." +fi + + set_safe_filemode_on_app From e27c496f8032bb8ef2623fd00533e8d7736f4e07 Mon Sep 17 00:00:00 2001 From: Andrew-Kang-G Date: Sat, 9 Nov 2024 14:51:44 +0900 Subject: [PATCH 07/11] fix : additional security exception fixes for Darwin --- apply-security.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apply-security.sh b/apply-security.sh index e779482..88ff98b 100644 --- a/apply-security.sh +++ b/apply-security.sh @@ -48,5 +48,8 @@ else echo "[NOTICE] Skipping chown command on Darwin (macOS) platform. See the README." fi - -set_safe_filemode_on_app +if [[ "$(uname)" != "Darwin" ]]; then + set_safe_filemode_on_app +else + echo "[NOTICE] Skipping chown command on Darwin (macOS) platform. See the README." +fi From f64c22e7c51b7aca392eff05021c7618296ac280 Mon Sep 17 00:00:00 2001 From: Andrew-Kang-G Date: Sat, 9 Nov 2024 14:57:51 +0900 Subject: [PATCH 08/11] fix : add yq installed more --- remove-all-images.sh | 1 + stop-all-containers.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/remove-all-images.sh b/remove-all-images.sh index f03af6a..4670620 100644 --- a/remove-all-images.sh +++ b/remove-all-images.sh @@ -2,6 +2,7 @@ set -eu source use-common.sh +check_yq_installed check_bash_version check_gnu_grep_installed check_gnu_sed_installed diff --git a/stop-all-containers.sh b/stop-all-containers.sh index 914b4d5..4755801 100644 --- a/stop-all-containers.sh +++ b/stop-all-containers.sh @@ -1,5 +1,6 @@ #!/bin/bash source use-common.sh +check_yq_installed check_bash_version check_gnu_grep_installed check_gnu_sed_installed From 64cd544e49c32b2ac6e7c8594b193a27d86a1e4d Mon Sep 17 00:00:00 2001 From: Andrew-Kang-G Date: Sat, 9 Nov 2024 15:00:03 +0900 Subject: [PATCH 09/11] fix : add yq installed more 2 --- emergency-consul-down-and-up.sh | 1 + emergency-nginx-down-and-up.sh | 1 + emergency-nginx-restart.sh | 2 ++ 3 files changed, 4 insertions(+) diff --git a/emergency-consul-down-and-up.sh b/emergency-consul-down-and-up.sh index 935ccc5..ccb06d4 100644 --- a/emergency-consul-down-and-up.sh +++ b/emergency-consul-down-and-up.sh @@ -2,6 +2,7 @@ set -eu source use-common.sh +check_yq_installed check_bash_version check_gnu_grep_installed check_gnu_sed_installed diff --git a/emergency-nginx-down-and-up.sh b/emergency-nginx-down-and-up.sh index 30b7476..badbd0c 100644 --- a/emergency-nginx-down-and-up.sh +++ b/emergency-nginx-down-and-up.sh @@ -3,6 +3,7 @@ set -eu source use-common.sh +check_yq_installed check_bash_version check_gnu_grep_installed check_gnu_sed_installed diff --git a/emergency-nginx-restart.sh b/emergency-nginx-restart.sh index a1abf35..cb90cea 100644 --- a/emergency-nginx-restart.sh +++ b/emergency-nginx-restart.sh @@ -2,6 +2,8 @@ set -eu source use-common.sh + +check_yq_installed check_bash_version check_gnu_grep_installed check_gnu_sed_installed From 776a3cfab3a488dd6708f227f3fc9bb937dc435e Mon Sep 17 00:00:00 2001 From: Andrew-Kang-G Date: Mon, 11 Nov 2024 00:13:18 +0900 Subject: [PATCH 10/11] doc : make docs clearer --- README.md | 6 +-- documents/Deploy-React-Project-with-DBGR.md | 46 ++++++++++++++++----- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 8c97ad8..0ede988 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ > One Simple Zero-Downtime Blue-Green Deployment with your Dockerfiles -- Use ``the latest Release version`` OR at least ``tagged versions`` for your production, NOT the latest commit of the 'main' branch. -- In production, place your project in a separate folder, not in the samples folder, as they are just examples. ## Table of Contents - [Process Summary](#process-summary) @@ -310,9 +308,9 @@ sudo bash run.sh - ```shell APP_URL=http://localhost:<--!host-port-number!--> PROJECT_PORT=<--!common-port-number!--> OR - PROJECT_PORT=[<--!host-port-number!-->,<--!new-project-port-number!-->] + PROJECT_PORT=[<--!host-port-number!-->,<--!internal-project-port-number!-->] ``` - - Additionally, the APP_URL parameter is used for 'check_availability_out_of_container' at [Structure](#Structure) + - Additionally, the `APP_URL` parameter is used for the ["External Integrity Check"](#process-summary) process. - You can set it as https://localhost:13000 or https://your-domain:13000 for production environments. (Both configurations are acceptable) - Moreover, the Runner parses the protocol (http or https), and if it is https, it checks for SSL certificates in the .docker/ssl directory on the host according to the following settings: - ```shell diff --git a/documents/Deploy-React-Project-with-DBGR.md b/documents/Deploy-React-Project-with-DBGR.md index 15c4db4..2a62d0a 100644 --- a/documents/Deploy-React-Project-with-DBGR.md +++ b/documents/Deploy-React-Project-with-DBGR.md @@ -17,14 +17,26 @@ - ``REDIRECT_HTTPS_TO_HTTP`` - .env file - ```dotenv + # Leave as it is HOST_IP=host.docker.internal + # Set this to 'real' in the .env file for production environments. + # This affects 'DOCKER_COMPOSE_REAL_SELECTIVE_VOLUMES' below + # If this is set to 'local', the whole projects is set to be Volumed. APP_ENV=real # It is recommended to enter your formal URL or IP, such as https://test.com, for the 'check_availability_out_of_container' test in the 'run.sh' script. # Both https://your-app.com:443 and https://localhost:443 are valid + # Docker-Blue-Runner recognizes if your App requires SSL in the Nginx router if this starts with 'https'. + # This URL is used for the "External Integrity Check" process. APP_URL=https://localhost:443 - USE_COMMERCIAL_SSL=false + # APP_URL=http://localhost:<--!host-port-number!--> + # PROJECT_PORT=<--!common-port-number!--> OR + # PROJECT_PORT=[<--!host-port-number!-->,<--!internal-project-port-number!-->] + PROJECT_PORT=[443,8360] + # In case USE_COMMERCIAL_SSL is 'false', the Runner generates self-signed SSL certificates. However, you should set any name for ``COMMERCIAL_SSL_NAME``. + # In case it is 'true', locate your commercial SSLs in the folder docker-blue-green-runner/.docker/ssl. See the comments in the .env above. + USE_COMMERCIAL_SSL=true # Your domain name is recommended. The files 'your-app.com.key', 'your-app.com.crt', and 'your-app.com.chained.crt' should be in place. COMMERCIAL_SSL_NAME=your-app.com @@ -59,31 +71,43 @@ DOCKER_FILE_LOCATION=/var/projects/your-app # This is for integrating health checkers such as "https://www.baeldung.com/spring-boot-actuators" - APP_HEALTH_CHECK_PATH=login - BAD_APP_HEALTH_CHECK_PATTERN=xxxxxxx - GOOD_APP_HEALTH_CHECK_PATTERN=Head + # This path is used for both internal and external health checks. + # Note: Do not include a leading slash ("/") at the start of the path. + # Example: "api/v1/health" (correct), "/api/v1/health" (incorrect) + APP_HEALTH_CHECK_PATH=api/v1/health + + # The BAD & GOOD conditions are checked using an "AND" condition. + # To ignore the "BAD_APP_HEALTH_CHECK_PATTERN", set it to a non-existing value (e.g., "###lsdladc"). + BAD_APP_HEALTH_CHECK_PATTERN=DOWN + + # Pattern required for a successful health check. + GOOD_APP_HEALTH_CHECK_PATTERN=UP + + # The following trick is just for skipping the check. + # APP_HEALTH_CHECK_PATH=login + # BAD_APP_HEALTH_CHECK_PATTERN=xxxxxxx + # GOOD_APP_HEALTH_CHECK_PATTERN=Head # This is for environment variables for docker-compose-app-${app_env}. DOCKER_COMPOSE_ENVIRONMENT={"TZ":"Asia/Seoul"} - # This goes with "docker build ... in the 'run.sh' script file", and the command always contain "HOST_IP" and "APP_ENV" above. - # docker exec -it CONTAINER_NAME cat /var/log/env_build_args.log - # The name "PROJECT_ROOT_IN_CONTAINER" is simply a convention used with your Dockerfile. You can change it if desired. + # [IMPORTANT] You can pass any variable to Step 2 of your Dockerfile using DOCKER_BUILD_ARGS, e.g., DOCKER_BUILD_ARGS={"PROJECT_ROOT_IN_CONTAINER":"/app"}." DOCKER_BUILD_ARGS={"PROJECT_ROOT_IN_CONTAINER":"/app"} # In the case of "REAL," the project is not synchronized in its entirety. The source codes that are required for only production are injected. - # For SSL, the host folder is recommended to be './.docker/ssl' to be synchronized with 'docker-compose-nginx-original.yml' + # For SSL, the host folder is recommended to be './.docker/ssl' to be synchronized with 'docker-compose-nginx-original.yml'. # [IMPORTANT] Run mkdir -p /var/projects/files/your-app/logs on your host machine DOCKER_COMPOSE_REAL_SELECTIVE_VOLUMES=["/var/projects/your-app/.docker/nginx/app.conf.ctmpl:/etc/nginx-template/app.conf.ctmpl","/var/projects/files/your-app/logs:/var/log/nginx"] # [IMPORTANT] Run mkdir -p /var/projects/files/nginx/logs on your host machine DOCKER_COMPOSE_NGINX_SELECTIVE_VOLUMES=["/var/projects/files/nginx/logs:/var/log/nginx"] DOCKER_COMPOSE_HOST_VOLUME_CHECK=false - + NGINX_CLIENT_MAX_BODY_SIZE=50M USE_MY_OWN_APP_YML=false SKIP_BUILDING_APP_IMAGE=false + # Docker-Swarm(stack) is currently a beta version. Use 'compose'. ORCHESTRATION_TYPE=compose ONLY_BUILDING_APP_IMAGE=false @@ -94,7 +118,7 @@ # ex. /docs/api-app.html NGINX_RESTRICTED_LOCATION=xxx - # If you set this to 'true', you won't need to configure SSL for your app. For instance, in a Spring Boot project, you won't have to create a ".jks" file. However, in rare situations, such as when it's crucial to secure all communication lines with SSL or when converting HTTPS to HTTP causes 'curl' errors, you might need to set it to 'false'.If you set this to 'true', you don't need to set SSL on your App like for example, for a Spring Boot project, you won't need to create the ".jks" file. However, in rare cases, such as ensuring all communication lines are SSL-protected, or when HTTPS to HTTP causes 'curl' errors, you might need to set it to 'false'. + # If you set this to 'true', you won't need to configure SSL for your app. For instance, in a Spring Boot project, you won't have to create a ".jks" file. However, in rare situations, such as when it's crucial to secure all communication lines with SSL or when converting HTTPS to HTTP causes 'curl' errors, you might need to set it to 'false'.If you set this to 'true', you don't need to set SSL on your App like for example, for a Spring Boot project, you won't need to create the ".jks" file. However, in rare cases, such as ensuring all communication lines are SSL-protected, or when HTTPS to HTTP causes 'curl' errors, you might need to set it to 'false'. # 1) true : [Request]--> https (external network) -->Nginx--> http (internal network) --> App # 2) false :[Request]--> https (external network) -->Nginx--> httpS (internal network) --> App # !!! [IMPORTANT] As your App container below is Http, this should be set to 'true'. @@ -103,7 +127,7 @@ NGINX_LOGROTATE_FILE_NUMBER=7 NGINX_LOGROTATE_FILE_SIZE=1M - # You can change the values below. These settings for security related to ``set-safe-permissions.sh`` at the root of Docker-Blue-Green-Runner. + # You can change the values below. These settings for security related to ``set-safe-permissions.sh`` at the root of Docker-Blue-Green-Runner. SHARED_VOLUME_GROUP_ID=1559 SHARED_VOLUME_GROUP_NAME=mba-shared-volume-group UIDS_BELONGING_TO_SHARED_VOLUME_GROUP_ID=1000,1001 From 2ba4d37849bd6ac89d446fab2c832c3db702ca75 Mon Sep 17 00:00:00 2001 From: Andrew-Kang-G Date: Thu, 14 Nov 2024 23:56:25 +0900 Subject: [PATCH 11/11] feature : Track Git SHA for your container DOCKER_BUILD_SHA_INSERT_GIT_ROOT= - Additionally, includes minor refactors --- .env.example.local | 3 + .env.example.real | 3 + .env.java.local | 3 + .env.java.real | 3 + .env.java.real.commercial.ssl.sample | 2 + .env.node.local | 3 + .env.php.local | 3 + .env.php.real | 4 ++ README.md | 13 ++-- apply-security.sh | 2 + check-current-states.sh | 18 ++++- check-source-integrity.sh | 2 + documents/Deploy-React-Project-with-DBGR.md | 2 +- documents/images/img6.png | Bin 0 -> 31697 bytes emergency-consul-down-and-up.sh | 3 +- emergency-nginx-down-and-up.sh | 2 +- emergency-nginx-restart.sh | 2 +- remove-all-images.sh | 3 +- rollback.sh | 2 + run.sh | 22 ++++-- set-safe-permissions.sh | 13 ---- stop-all-containers.sh | 3 +- use-app.sh | 27 ++++++- use-common.sh | 76 +++++++++++++++++++- 24 files changed, 179 insertions(+), 35 deletions(-) create mode 100644 documents/images/img6.png delete mode 100644 set-safe-permissions.sh diff --git a/.env.example.local b/.env.example.local index 963ba65..272ae3d 100644 --- a/.env.example.local +++ b/.env.example.local @@ -52,6 +52,9 @@ GOOD_APP_HEALTH_CHECK_PATTERN=xxx # This is for environment variables for docker-compose-app-${app_env}. DOCKER_COMPOSE_ENVIRONMENT={"XDEBUG_CONFIG":"idekey=IDE_DEBUG","PHP_IDE_CONFIG":"serverName=laravel-crud-boilerplate"} DOCKER_BUILD_ARGS={} +DOCKER_BUILD_LABELS=[] +DOCKER_BUILD_SHA_INSERT_GIT_ROOT= + DOCKER_COMPOSE_NGINX_SELECTIVE_VOLUMES=[] DOCKER_COMPOSE_HOST_VOLUME_CHECK=false diff --git a/.env.example.real b/.env.example.real index 4485309..f7194a7 100644 --- a/.env.example.real +++ b/.env.example.real @@ -54,6 +54,9 @@ GOOD_APP_HEALTH_CHECK_PATTERN=xxx # This is for environment variables for docker-compose-app-${app_env}. DOCKER_COMPOSE_ENVIRONMENT={} DOCKER_BUILD_ARGS={} +DOCKER_BUILD_LABELS=[] +DOCKER_BUILD_SHA_INSERT_GIT_ROOT= + # This is overwritten on docker-compose-${project_name}-${app_env}.yml DOCKER_COMPOSE_REAL_SELECTIVE_VOLUMES=[] # This is added on docker-compose-${project_name}-nginx.yml diff --git a/.env.java.local b/.env.java.local index 5d7f2a2..3889b6d 100644 --- a/.env.java.local +++ b/.env.java.local @@ -47,6 +47,9 @@ DOCKER_COMPOSE_ENVIRONMENT={"TZ":"Asia/Seoul"} # This goes with "docker build ... in the 'run.sh' script file", and the command always contain "HOST_IP" and "APP_ENV" above. # docker exec -it CONTAINER_NAME cat /var/log/env_build_args.log DOCKER_BUILD_ARGS={"DOCKER_BUILDKIT":"1","PROJECT_ROOT_IN_CONTAINER":"/var/www/server/spring-sample-h-auth","APP_ENV":"local"} +DOCKER_BUILD_LABELS=[] +DOCKER_BUILD_SHA_INSERT_GIT_ROOT= + DOCKER_COMPOSE_NGINX_SELECTIVE_VOLUMES=[] DOCKER_COMPOSE_HOST_VOLUME_CHECK=false diff --git a/.env.java.real b/.env.java.real index 9e44386..cbbd7ef 100644 --- a/.env.java.real +++ b/.env.java.real @@ -48,6 +48,9 @@ DOCKER_COMPOSE_ENVIRONMENT={"TZ":"Asia/Seoul"} # This goes with "docker build ... in the 'run.sh' script file", and the command always contain "HOST_IP" and "APP_ENV" above. # docker exec -it CONTAINER_NAME cat /var/log/env_build_args.log DOCKER_BUILD_ARGS={"DOCKER_BUILDKIT":"1","PROJECT_ROOT_IN_CONTAINER":"/var/www/server/spring-sample-h-auth","APP_ENV":"production"} +DOCKER_BUILD_LABELS=["foo=happy","bar=sad"] +DOCKER_BUILD_SHA_INSERT_GIT_ROOT= + DOCKER_COMPOSE_REAL_SELECTIVE_VOLUMES=["./samples/spring-sample-h-auth/logs:/var/www/files"] DOCKER_COMPOSE_NGINX_SELECTIVE_VOLUMES=["./shared/nginx-error-logs:/var/log/nginx"] DOCKER_COMPOSE_HOST_VOLUME_CHECK=false diff --git a/.env.java.real.commercial.ssl.sample b/.env.java.real.commercial.ssl.sample index ed83a89..870fe9b 100644 --- a/.env.java.real.commercial.ssl.sample +++ b/.env.java.real.commercial.ssl.sample @@ -47,6 +47,8 @@ DOCKER_COMPOSE_ENVIRONMENT={"TZ":"Asia/Seoul"} # This goes with "docker build ... in the 'run.sh' script file", and the command always contain "HOST_IP" and "APP_ENV" above. # 2) ''/var/web/project/spring-sample-h-auth' is here as well DOCKER_BUILD_ARGS={"DOCKER_BUILDKIT":"1","PROJECT_ROOT_IN_CONTAINER":"/var/www/server/spring-sample-h-auth","FILE_STORAGE_ROOT_IN_CONTAINER":"/var/www/files","JVM_XMS":"2048","JVM_XMX":"4096"} +DOCKER_BUILD_LABELS=["foo.mylabel=happy","bar.mylabel=happy"] +DOCKER_BUILD_SHA_INSERT_GIT_ROOT=/var/www/server/spring-sample-h-auth # 3) ''/var/web/project/spring-sample-h-auth' is here as well. The thing is you should locate 'application.properties', 'logback-spring.xml', 'yourdomain.com.jks' on the './src/main/resource' folder. DOCKER_COMPOSE_REAL_SELECTIVE_VOLUMES=["/var/web/files/spring-sample-h-auth:/var/www/files","/var/web/project/spring-sample-h-auth/src/main/resources:/var/www/server/spring-sample-h-auth/src/main/resources"] diff --git a/.env.node.local b/.env.node.local index 1333c86..cb1df9d 100644 --- a/.env.node.local +++ b/.env.node.local @@ -44,6 +44,9 @@ GOOD_APP_HEALTH_CHECK_PATTERN=docs # This is for environment variables for docker-compose-app-${app_env}. DOCKER_COMPOSE_ENVIRONMENT={"MONGODB_URL":"mongodb://host.docker.internal:27017/node-boilerplate","NODE_ENV":"development"} DOCKER_BUILD_ARGS={} +DOCKER_BUILD_LABELS=["foo=happy","bar=sad"] +DOCKER_BUILD_SHA_INSERT_GIT_ROOT= + DOCKER_COMPOSE_NGINX_SELECTIVE_VOLUMES=[] DOCKER_COMPOSE_HOST_VOLUME_CHECK=false diff --git a/.env.php.local b/.env.php.local index 8494a7c..4622937 100644 --- a/.env.php.local +++ b/.env.php.local @@ -48,6 +48,9 @@ DOCKER_COMPOSE_ENVIRONMENT={"XDEBUG_CONFIG":"idekey=IDE_DEBUG","PHP_IDE_CONFIG": # This goes with "docker build ... in the 'run.sh' script file", and the command always contain "HOST_IP" and "APP_ENV" above. # docker exec -it CONTAINER_NAME cat /var/log/env_build_args.log DOCKER_BUILD_ARGS={"SAMPLE":"YAHOO","SAMPLE2":"YAHOO2"} +DOCKER_BUILD_LABELS=["foo=happy","bar=sad"] +DOCKER_BUILD_SHA_INSERT_GIT_ROOT= + DOCKER_COMPOSE_NGINX_SELECTIVE_VOLUMES=[] DOCKER_COMPOSE_HOST_VOLUME_CHECK=false diff --git a/.env.php.real b/.env.php.real index 2a394c5..3726efa 100644 --- a/.env.php.real +++ b/.env.php.real @@ -48,6 +48,10 @@ DOCKER_COMPOSE_ENVIRONMENT={"XDEBUG_CONFIG":"idekey=IDE_DEBUG","PHP_IDE_CONFIG": # This goes with "docker build ... in the 'run.sh' script file", and the command always contain "HOST_IP" and "APP_ENV" above. # docker exec -it CONTAINER_NAME cat /var/log/env_build_args.log DOCKER_BUILD_ARGS={"SAMPLE":"YAHOO","SAMPLE2":"YAHOO2","shared_volume_group_id":"1351","shared_volume_group_name":"laravel-shared-volume-group"} +DOCKER_BUILD_LABELS=["foo=happy","bar=sad"] +# Your Git's commit SHA will be added as a label to DOCKER_BUILD_LABELS when your container is built. +DOCKER_BUILD_SHA_INSERT_GIT_ROOT= + # In the case of "REAL," the project is not synchronized in its entirety. The source codes that are required for only production are injected. # For SSL, the host folder is recommended to be './.docker/ssl' to be synchronized with 'docker-compose-nginx-original.yml' DOCKER_COMPOSE_REAL_SELECTIVE_VOLUMES=["./shared/app-error-logs:/var/www/app/storage/logs","./.docker/ssl:/etc/apache2/ssl"] diff --git a/README.md b/README.md index 0ede988..754bc2f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ > One Simple Zero-Downtime Blue-Green Deployment with your Dockerfiles - ## Table of Contents - [Process Summary](#process-summary) - [Features](#features) @@ -31,7 +30,7 @@ - [Consul](#consul) - [USE_NGINX_RESTRICTION on .env](#use_nginx_restriction-on-env) - [Advanced](#advanced) -- [Gitlab Container Registry](#gitlab-container-registry) +- [GitLab Container Registry (Production)](#gitlab-container-registry-production) - [Upload Image (CI/CD Server -> Git)](#upload-image-cicd-server---git) - [Download Image (Git -> Production Server)](#download-image-git---production-server) - [Extra Information](#extra-information) @@ -80,7 +79,7 @@ graph TD; ``` ![img5.png](/documents/images/img5.png) - +![img6.png](documents/images/img6.png) ## Features - **No Unpredictable Errors in Reverse Proxy and Deployment** @@ -348,6 +347,10 @@ NGINX_RESTART=false # Setting this to 'true' is not recommended for normal operation as it results in prolonged downtime. CONSUL_RESTART=false +# Specify the location of the .git folder for your project here to enable tracking through container labels. +# To track, simply run `bash check-current_states.sh`. +DOCKER_BUILD_SHA_INSERT_GIT_ROOT= + # Not recommended for normal operation as it leads to a long downtime. # If this is set to true, it entails running 'stop-all-containers.sh & remove-all-images.sh'. # In case your project is renamed or moved to another folder, Docker may not work properly. @@ -451,6 +454,8 @@ bash check-current-states.sh [DEBUG] ! Checked which (Blue OR Green) is currently running... (Final Check) : blue_score : 130, green_score : 27, state : blue, new_state : green, state_for_emergency : blue, new_upstream : https://PROJECT_NAME:8300. ``` - The higher the score a state receives, the more likely it is to be the currently running state. So the updated App should be deployed as the non-occupied state(new_state). + +- ![img6.png](documents/images/img6.png) ### Emergency - Nginx (like when Nginx is NOT booted OR 502 error...) @@ -542,7 +547,7 @@ bash check-source-integrity.sh - ```bash run.sh``` -## Gitlab Container Registry +## Gitlab Container Registry (Production) ### Upload Image (CI/CD Server -> Git) - In case you run the command ``push-to-git.sh``, ``docker-blue-green-runner`` pushes one of ``Blue or Green`` images which is currently running to the address above of the Gitlab Container Registry. diff --git a/apply-security.sh b/apply-security.sh index 88ff98b..9227703 100644 --- a/apply-security.sh +++ b/apply-security.sh @@ -2,9 +2,11 @@ set -eu source use-common.sh + check_bash_version check_gnu_grep_installed check_gnu_sed_installed +check_yq_installed check_git_docker_compose_commands_exist cache_global_vars diff --git a/check-current-states.sh b/check-current-states.sh index 0f6d7fc..73ac601 100644 --- a/check-current-states.sh +++ b/check-current-states.sh @@ -2,10 +2,26 @@ set -eu source use-common.sh + check_bash_version check_gnu_grep_installed check_gnu_sed_installed +check_yq_installed check_git_docker_compose_commands_exist +# Load global variables +cache_global_vars + +# Define container name +container_name="${project_name}-${state}" + +echo "[NOTICE] Project Name: ${project_name}" +display_emphasized_message "[NOTICE] Current State: ${state}" +display_emphasized_message "[NOTICE] Container Name: ${container_name}" + + +# Call the function +display_emphasized_message "$(print_git_sha_and_message "$container_name" "$docker_build_sha_insert_git_root")" -cache_global_vars \ No newline at end of file +# echo "[NOTICE] All labels inside the Container $container_name" +# docker inspect -f '{{json .Config.Labels}}' "$container_name" 2>/dev/null | yq -P \ No newline at end of file diff --git a/check-source-integrity.sh b/check-source-integrity.sh index fa33108..8c84132 100644 --- a/check-source-integrity.sh +++ b/check-source-integrity.sh @@ -2,9 +2,11 @@ set -eu source use-common.sh + check_bash_version check_gnu_grep_installed check_gnu_sed_installed +check_yq_installed check_git_docker_compose_commands_exist git config apply.whitespace nowarn diff --git a/documents/Deploy-React-Project-with-DBGR.md b/documents/Deploy-React-Project-with-DBGR.md index 2a62d0a..d961f96 100644 --- a/documents/Deploy-React-Project-with-DBGR.md +++ b/documents/Deploy-React-Project-with-DBGR.md @@ -127,7 +127,7 @@ NGINX_LOGROTATE_FILE_NUMBER=7 NGINX_LOGROTATE_FILE_SIZE=1M - # You can change the values below. These settings for security related to ``set-safe-permissions.sh`` at the root of Docker-Blue-Green-Runner. + # You can change the values below. These settings for security related to ``apply-security.sh`` at the root of Docker-Blue-Green-Runner. SHARED_VOLUME_GROUP_ID=1559 SHARED_VOLUME_GROUP_NAME=mba-shared-volume-group UIDS_BELONGING_TO_SHARED_VOLUME_GROUP_ID=1000,1001 diff --git a/documents/images/img6.png b/documents/images/img6.png new file mode 100644 index 0000000000000000000000000000000000000000..eac609aa58e2aea45f594cf79a59ea5e42f45e80 GIT binary patch literal 31697 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 OeQGM2%K0bFuKzzQcF@fL literal 0 HcmV?d00001 diff --git a/emergency-consul-down-and-up.sh b/emergency-consul-down-and-up.sh index ccb06d4..88e55ee 100644 --- a/emergency-consul-down-and-up.sh +++ b/emergency-consul-down-and-up.sh @@ -2,10 +2,11 @@ set -eu source use-common.sh -check_yq_installed + check_bash_version check_gnu_grep_installed check_gnu_sed_installed +check_yq_installed check_git_docker_compose_commands_exist diff --git a/emergency-nginx-down-and-up.sh b/emergency-nginx-down-and-up.sh index badbd0c..995363a 100644 --- a/emergency-nginx-down-and-up.sh +++ b/emergency-nginx-down-and-up.sh @@ -3,10 +3,10 @@ set -eu source use-common.sh -check_yq_installed check_bash_version check_gnu_grep_installed check_gnu_sed_installed +check_yq_installed check_git_docker_compose_commands_exist echo "[NOTICE] Substituting CRLF with LF to prevent possible CRLF errors..." diff --git a/emergency-nginx-restart.sh b/emergency-nginx-restart.sh index cb90cea..e28f26e 100644 --- a/emergency-nginx-restart.sh +++ b/emergency-nginx-restart.sh @@ -3,10 +3,10 @@ set -eu source use-common.sh -check_yq_installed check_bash_version check_gnu_grep_installed check_gnu_sed_installed +check_yq_installed check_git_docker_compose_commands_exist echo "[NOTICE] Substituting CRLF with LF to prevent possible CRLF errors..." diff --git a/remove-all-images.sh b/remove-all-images.sh index 4670620..a9d947a 100644 --- a/remove-all-images.sh +++ b/remove-all-images.sh @@ -2,10 +2,11 @@ set -eu source use-common.sh -check_yq_installed + check_bash_version check_gnu_grep_installed check_gnu_sed_installed +check_yq_installed check_git_docker_compose_commands_exist git config apply.whitespace nowarn diff --git a/rollback.sh b/rollback.sh index 0c013b4..46676fd 100644 --- a/rollback.sh +++ b/rollback.sh @@ -2,9 +2,11 @@ set -eu source use-common.sh + check_bash_version check_gnu_grep_installed check_gnu_sed_installed +check_yq_installed check_git_docker_compose_commands_exist sudo sed -i -e "s/\r$//g" $(basename $0) diff --git a/run.sh b/run.sh index 6712f1e..be4a8ba 100644 --- a/run.sh +++ b/run.sh @@ -10,9 +10,9 @@ display_checkpoint_message "Checking versions for supporting libraries...(1%)" check_bash_version check_gnu_grep_installed check_gnu_sed_installed +check_yq_installed check_git_docker_compose_commands_exist -check_yq_installed sudo chmod a+x *.sh @@ -195,8 +195,14 @@ _main() { display_planned_transition "$initially_cached_old_state" "$new_state" sleep 2 + if [[ "${git_image_load_from}" == "build" && -n "${project_git_sha}" && -n "${docker_build_sha_insert_git_root}" ]]; then + commit_message=$(get_commit_message "$project_git_sha" "$docker_build_sha_insert_git_root") + display_checkpoint_message "Will build this GIT version: $project_git_sha : $commit_message" + sleep 1 + fi + ## App - display_checkpoint_message "Setting up the app configuration 'yml' for orchestration type: ${orchestration_type}... (5%)" + display_checkpoint_message "Setting up the app configuration 'yml' for orchestration type: ${orchestration_type}... (6%)" initiate_docker_compose_file apply_env_service_name_onto_app_yaml apply_docker_compose_environment_onto_app_yaml @@ -313,6 +319,7 @@ _main() { echo "[NOTICE] For safety, finally check Consul pointing before stopping the previous container (${initially_cached_old_state})." local consul_pointing=$(docker exec ${project_name}-nginx curl ${consul_key_value_store}?raw 2>/dev/null || echo "failed") if [[ ${consul_pointing} != ${initially_cached_old_state} ]]; then + if [[ ${orchestration_type} != 'stack' ]]; then docker-compose -f docker-${orchestration_type}-${project_name}-${app_env}.yml stop ${project_name}-${initially_cached_old_state} echo "[NOTICE] The previous (${initially_cached_old_state}) container (initially_cached_old_state) has been stopped because the deployment was successful. (If NGINX_RESTART=true or CONSUL_RESTART=true, existing containers have already been terminated in the load_all_containers function.)" @@ -320,14 +327,17 @@ _main() { docker stack rm ${project_name}-${initially_cached_old_state} echo "[NOTICE] The previous (${initially_cached_old_state}) service (initially_cached_old_state) has been stopped because the deployment was successful. (If NGINX_RESTART=true or CONSUL_RESTART=true, existing containers have already been terminated in the load_all_containers function.)" fi + + display_checkpoint_message "CURRENT APP_URL: ${app_url}. Run 'bash check-current-states.sh' whenever you want to check the deployment status and Git SHA." + print_git_sha_and_message "${project_name}-${new_state}" "$docker_build_sha_insert_git_root" + + echo "[NOTICE] Delete : images." + docker rmi $(docker images -f "dangling=true" -q) || echo "[NOTICE] Any images in use will not be deleted." + else echo "[NOTICE] The previous (${initially_cached_old_state}) container (initially_cached_old_state) has NOT been stopped because the current Consul Pointing is ${consul_pointing}." fi - echo "[NOTICE] Delete : images." - docker rmi $(docker images -f "dangling=true" -q) || echo "[NOTICE] Any images in use will not be deleted." - - display_checkpoint_message "[NOTICE] APP_URL : ${app_url}" } _main diff --git a/set-safe-permissions.sh b/set-safe-permissions.sh deleted file mode 100644 index 7ef4920..0000000 --- a/set-safe-permissions.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -eu - -source use-common.sh -check_gnu_sed_installed - -sudo sed -i -e "s/\r$//g" $(basename $0) || sed -i -e "s/\r$//g" $(basename $0) -sudo chmod 770 * -sudo chmod 770 .env.* -sudo chmod 770 tests/* -sudo chmod -R 770 .docker/nginx -# This is temporary. You should set your SSLs to be such as 644 or 640 on your SSL folder. -sudo chmod -R 770 .docker/ssl diff --git a/stop-all-containers.sh b/stop-all-containers.sh index 4755801..9575b15 100644 --- a/stop-all-containers.sh +++ b/stop-all-containers.sh @@ -1,9 +1,10 @@ #!/bin/bash source use-common.sh -check_yq_installed + check_bash_version check_gnu_grep_installed check_gnu_sed_installed +check_yq_installed check_git_docker_compose_commands_exist sudo sed -i -e "s/\r$//g" $(basename $0) diff --git a/use-app.sh b/use-app.sh index 245337c..0cca2ec 100644 --- a/use-app.sh +++ b/use-app.sh @@ -153,14 +153,36 @@ load_app_docker_image() { echo "[NOTICE] Build the image with ${docker_file_location}/${docker_file_name} (using cache)" local env_build_args=$(make_docker_build_arg_strings) echo "[NOTICE] DOCKER_BUILD_ARGS on the .env : ${env_build_args}" + echo "[NOTICE] DOCKER_BUILD_LABELS on the .env : ${docker_build_labels}" + + # Convert DOCKER_BUILD_LABELS to Docker --label arguments + local label_args="" + IFS=',' read -r -a labels <<< "$docker_build_labels" + + for label in "${labels[@]}"; do + # Ensure labels are in correct format (e.g., key=value) + formatted_label=$(echo "$label" | sed -e 's/^\[//' -e 's/\]$//' -e 's/^"//' -e 's/"$//' | xargs) + + # Only add to label_args if formatted_label is not empty + if [[ -n "$formatted_label" ]]; then + label_args+="--label $formatted_label " + fi + done + + # Trim whitespace and check if label_args is just "--label" + if [[ $(echo "$label_args" | xargs) == "--label" ]]; then + label_args="" + fi + + echo "[NOTICE] Final DOCKER_BUILD_LABELS on the .env : ${label_args} " if [[ ${docker_layer_corruption_recovery} == true ]]; then echo "[NOTICE] Docker Build Command : docker build --no-cache --tag ${project_name}:latest --build-arg server="${app_env}" ${env_build_args} -f ${docker_file_name} -m ${docker_build_memory_usage} ." - cd ${docker_file_location} && docker build --no-cache --tag ${project_name}:latest --build-arg server="${app_env}" ${env_build_args} -f ${docker_file_name} -m ${docker_build_memory_usage} . || exit 1 + cd ${docker_file_location} && docker build --no-cache --tag ${project_name}:latest ${label_args} --build-arg server="${app_env}" ${env_build_args} -f ${docker_file_name} -m ${docker_build_memory_usage} . || exit 1 cd - else echo "[NOTICE] Docker Build Command : docker build --build-arg DISABLE_CACHE=${CUR_TIME} --tag ${project_name}:latest --build-arg server="${app_env}" --build-arg HOST_IP="${HOST_IP}" ${env_build_args} -f ${docker_file_name} -m ${docker_build_memory_usage} ." - cd ${docker_file_location} && docker build --build-arg DISABLE_CACHE=${CUR_TIME} --tag ${project_name}:latest --build-arg server="${app_env}" --build-arg HOST_IP="${HOST_IP}" ${env_build_args} -f ${docker_file_name} -m ${docker_build_memory_usage} . || exit 1 + cd ${docker_file_location} && docker build --build-arg DISABLE_CACHE=${CUR_TIME} --tag ${project_name}:latest ${label_args} --build-arg server="${app_env}" --build-arg HOST_IP="${HOST_IP}" ${env_build_args} -f ${docker_file_name} -m ${docker_build_memory_usage} . || exit 1 cd - fi @@ -273,6 +295,7 @@ check_availability_inside_container(){ echo "[NOTICE] [Internal Integrity Check : will deploy ${check_state}] In the ${container_name} Container, conduct the Connection Check (localhost:${app_port} --timeout=${2}). (If this is delayed, run ' docker logs -f ${container_name} (compose), docker service ps ${project_name}-${check_state}_${project_name}-${check_state} (stack) ' to check the status." >&2 echo "[NOTICE] [Internal Integrity Check : will deploy ${check_state}] Current status (inside Container) : \n $(docker logs ${container_name})" >&2 + echo "[NOTICE] wait_for_it ( https://github.com/vishnubob/wait-for-it ) will do the Check" >&2 local wait_for_it_re=$(docker exec -w ${project_location} ${container_name} ./wait-for-it.sh localhost:${app_port} --timeout=${2}) || (echo "[ERROR] Failed in running (CONTAINER : ${project_location}/wait-for-it.sh)" >&2 && echo "false" && return) if [[ $? != 0 ]]; then echo "[ERROR] Failed in getting the correct return from wait-for-it.sh. (${wait_for_it_re})" >&2 diff --git a/use-common.sh b/use-common.sh index 7547517..7fac559 100644 --- a/use-common.sh +++ b/use-common.sh @@ -4,6 +4,10 @@ set -eu source ./validator.sh source ./use-states.sh +display_emphasized_message() { + local message=$1 + printf "\033[1;34m%s\033[0m\n" "$message" # Display message in bold blue +} display_checkpoint_message() { local message=$1 @@ -57,6 +61,59 @@ to_lower() { echo "$1" | tr '[:upper:]' '[:lower:]' } +get_git_sha_or_none() { + local target_dir=$1 # Accepts the directory as the first argument + local sha="" + + # Check if the directory exists and is a Git repository + if [ -d "$target_dir" ] && [ -d "$target_dir/.git" ]; then + # Navigate to the target directory + pushd "$target_dir" > /dev/null + # Retrieve the SHA value and store it in the variable + sha=$(git rev-parse HEAD) + # Return to the original directory + popd > /dev/null + else + echo "NONE" + fi + + # Output the SHA value + echo "$sha" +} + +# Function to retrieve the SHA label from the container +get_container_git_sha() { + local container_name=$1 + docker inspect -f '{{ index .Config.Labels "project.git.sha" }}' "$container_name" 2>/dev/null || echo "" +} + +# Function to retrieve the commit message for a given SHA +get_commit_message() { + local sha=$1 + local git_location=$2 + git -C "$git_location" log -1 --pretty=format:"%s" "$sha" 2>/dev/null || echo "Commit message NOT found (docker_build_sha_insert_git_root : $git_location)" +} + +# Function to print SHA and commit message +print_git_sha_and_message() { + local container_name=$1 + local git_location=$2 + + # Retrieve SHA + local sha=$(get_container_git_sha "$container_name") + + # Check if SHA was found + if [[ -n "$sha" ]]; then + # Retrieve commit message + local commit_message=$(get_commit_message "$sha" "$git_location") + + echo "[NOTICE] Git SHA for the Current Container: $sha" + echo "[NOTICE] Git Commit Message for the Current SHA: $commit_message" + else + echo "[NOTICE] The 'project.git.sha' label is missing for the container '${container_name}', so the container's Git information cannot be retrieved. It appears the container was created with 'DOCKER_BUILD_SHA_INSERT_GIT_ROOT' left empty in the .env file." + fi +} + set_expose_and_app_port(){ if [[ -z ${1} ]] @@ -106,6 +163,19 @@ cache_non_dependent_global_vars() { docker_compose_environment=$(get_value_from_env "DOCKER_COMPOSE_ENVIRONMENT") docker_build_args=$(get_value_from_env "DOCKER_BUILD_ARGS") + # Get DOCKER_BUILD_LABELS and Git SHA + docker_build_labels=$(get_value_from_env "DOCKER_BUILD_LABELS") + + # and Git SHA + docker_build_sha_insert_git_root=$(get_value_from_env "DOCKER_BUILD_SHA_INSERT_GIT_ROOT") + + + project_git_sha="" + if [[ -n "${docker_build_sha_insert_git_root}" ]]; then + project_git_sha=$(get_git_sha_or_none "${docker_build_sha_insert_git_root}") + docker_build_labels="${docker_build_labels},project.git.sha=${project_git_sha}" + fi + consul_key_value_store=$(get_value_from_env "CONSUL_KEY_VALUE_STORE") consul_key=$(echo ${consul_key_value_store} | cut -d "/" -f6)\\/$(echo ${consul_key_value_store} | cut -d "/" -f7) @@ -439,7 +509,7 @@ get_value_from_env(){ value=$(echo $value | sed -e 's/\r//g') if [[ -z ${value} ]]; then - echo "[WARNING] ${value} for the key ${1} is empty .env." >&2 + echo "[WARNING] The value for the key ${1} is empty (value : ${value}) .env." >&2 fi echo ${value} # return. @@ -483,7 +553,7 @@ check_empty_env_values(){ value="$(echo -e "${value}" | sed -e 's/^[[:space:]]*|[[:space:]]*$//')" - if [[ ${value} == '' && ${key} != "CONTAINER_SSL_VOLUME_PATH" && ${key} != "ADDITIONAL_PORTS" && ${key} != "UIDS_BELONGING_TO_SHARED_VOLUME_GROUP_ID" ]]; then + if [[ ${value} == '' && ${key} != "CONTAINER_SSL_VOLUME_PATH" && ${key} != "ADDITIONAL_PORTS" && ${key} != "UIDS_BELONGING_TO_SHARED_VOLUME_GROUP_ID" && ${key} != "DOCKER_BUILD_LABELS" && ${key} != "DOCKER_BUILD_SHA_INSERT_GIT_ROOT" ]]; then empty_keys+=(${key}) fi @@ -711,4 +781,4 @@ stop_and_remove_container() { else echo "[NOTICE] Container ${container_name} does not exist." fi -} \ No newline at end of file +}