This commit is contained in:
Chu Văn Hải committed 2025-12-05 13:37:13 +07:00
commit 39d06135c3
29 files changed
+6840

No files matched your search

+378
View File
@@ -0,0 +1,378 @@
# ============================================================================
# .dockerignore - Optimize Docker Build Context
# ============================================================================
# This file excludes unnecessary files from Docker build context to:
# - Reduce build time
# - Minimize image size
# - Improve cache efficiency
# - Enhance security (exclude sensitive files)
# ============================================================================
# ============================================================================
# VERSION CONTROL
# ============================================================================
.git
.gitignore
.gitattributes
.gitmodules
.github/
.gitlab/
.gitlab-ci.yml
.gitlab-ci.include.yml
# ============================================================================
# NODE.JS & PACKAGE MANAGERS
# ============================================================================
# Dependencies (will be installed in container)
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.npm/
.yarn/
.pnpm/
.pnpm-store/
pnpm-debug.log*
# Package manager lock files (keep these for consistent installs)
# NOTE: Uncomment if you want to exclude lock files (not recommended)
# package-lock.json
# yarn.lock
# pnpm-lock.yaml
# Node.js runtime files
.node_repl_history
.node_tmp/
# ============================================================================
# FRAMEWORK-SPECIFIC BUILD OUTPUTS
# ============================================================================
# Next.js
.next/
out/
.vercel/
# React.js / Vite
build/
dist/
# NestJS
dist/
# Angular
dist/
.angular/
# Nuxt.js
.nuxt/
.output/
# SvelteKit
.svelte-kit/
# ============================================================================
# DEVELOPMENT & TESTING
# ============================================================================
# Test coverage
coverage/
.coverage/
.nyc_output/
.jest/
# Testing
*.test.js.snap
__tests__/
test/
tests/
spec/
specs/
# Storybook
.storybook/
storybook-static/
# Cypress
cypress/videos/
cypress/screenshots/
cypress/downloads/
# Playwright
playwright-report/
test-results/
# ============================================================================
# ENVIRONMENT & CONFIGURATION
# ============================================================================
# Environment files (security risk)
.env
.env.*
!.env.example
!.env.template
# Local environment
.local
.cache/
# Configuration files
.eslintcache
.stylelintcache
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# TypeScript
*.tsbuildinfo
.tscache/
# ============================================================================
# CI/CD & DEPLOYMENT
# ============================================================================
# CI/CD files
ci/
.circleci/
.travis.yml
.appveyor.yml
azure-pipelines.yml
buildspec.yml
cloudbuild.yaml
# Docker
Dockerfile.*
!Dockerfile
docker-compose*.yml
.dockerignore
# Kubernetes
k8s/
kubernetes/
*.yaml
*.yml
!package.json
!package-lock.json
!yarn.lock
!pnpm-lock.yaml
# Terraform
*.tf
*.tfstate
*.tfvars
.terraform/
# Ansible
inventory/
playbooks/
roles/
ansible.cfg
requirements.yml
# Helm
charts/
helm/
# ============================================================================
# DEVELOPMENT TOOLS & EDITORS
# ============================================================================
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# Vim
.vim/
.vimrc
# Emacs
.emacs.d/
.emacs
# Sublime Text
*.sublime-*
# WebStorm
.webstorm/
# VS Code extensions
.vscode-test/
# ============================================================================
# OPERATING SYSTEM
# ============================================================================
# macOS
.DS_Store
.AppleDouble
.LSOverride
Icon?
._*
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Windows
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
*.stackdump
[Dd]esktop.ini
$RECYCLE.BIN/
*.cab
*.msi
*.msix
*.msm
*.msp
*.lnk
# Linux
*~
.fuse_hidden*
.directory
.Trash-*
.nfs*
# ============================================================================
# LOGS & TEMPORARY FILES
# ============================================================================
# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Runtime data
pids/
*.pid
*.seed
*.pid.lock
# Temporary folders
tmp/
temp/
.tmp/
.temp/
# ============================================================================
# DOCUMENTATION & ASSETS
# ============================================================================
# Documentation
docs/
documentation/
README*.md
CHANGELOG*.md
CONTRIBUTING*.md
LICENSE*
SECURITY*.md
CODE_OF_CONDUCT*.md
# Design assets (large files)
*.psd
*.ai
*.sketch
*.fig
*.xd
# Media files (if not needed in production)
# *.png
# *.jpg
# *.jpeg
# *.gif
# *.svg
# *.ico
# *.webp
# ============================================================================
# MONITORING & ANALYTICS
# ============================================================================
# Bundle analyzers
.webpack-bundle-analyzer/
bundle-analyzer-report.html
# Performance monitoring
.lighthouse/
lighthouse-report.html
# ============================================================================
# SECURITY & SECRETS
# ============================================================================
# Security scan results
security-audit.json
vulnerability-report.json
# Certificates and keys
*.pem
*.key
*.crt
*.cert
*.p12
*.pfx
# Secrets
secrets/
.secrets/
# ============================================================================
# BACKUP & ARCHIVE
# ============================================================================
# Backup files
*.bak
*.backup
*.old
*.orig
# Archive files
*.tar
*.tar.gz
*.zip
*.rar
*.7z
# ============================================================================
# CUSTOM PROJECT EXCLUSIONS
# ============================================================================
# Add your project-specific exclusions here
# Examples:
# assets/raw/
# scripts/dev/
# mock-data/
# Generated files
# auto-generated/
# generated/
# Large datasets
# data/
# datasets/
# ============================================================================
# KEEP THESE FILES (Explicitly include important files)
# ============================================================================
# Ensure these files are included even if excluded above
!package.json
!yarn.lock
!package-lock.json
!pnpm-lock.yaml
!tsconfig.json
!next.config.js
!nuxt.config.js
!vite.config.js
!svelte.config.js
!tailwind.config.js
!postcss.config.js
!babel.config.js
!webpack.config.js
!rollup.config.js
!jest.config.js
!cypress.config.js
!playwright.config.js
# ============================================================================
# END OF .dockerignore
# ============================================================================
+177
View File
@@ -0,0 +1,177 @@
# This is a reusable Gitea Actions workflow.
# It's designed to be called by other repositories to standardize the deployment process.
# It performs two main jobs:
# 1. Build: Builds a Docker image from the calling repository's Dockerfile and pushes it to a registry.
# 2. Deploy: Runs the ansible-gitea playbook to deploy the newly built image to a target server.
name: Reusable CI/CD
# This workflow is triggered by a call from another workflow.
on:
workflow_call:
# ------------------ INPUTS ------------------
# These are parameters that the calling workflow must provide.
inputs:
# The name of the application/service being deployed.
# Used to find project-specific variables in ansible.
app_name:
description: 'The application name'
required: true
type: string
# The deployment environment (e.g., staging, production).
deploy_env:
description: 'The deployment environment'
required: true
type: string
# ------------------ SECRETS ------------------
# These are secrets that the calling workflow must pass.
# They should be configured in the Gitea organization or repository settings.
secrets:
# Address of the target host(s) for Ansible.
SERVER_HOST:
description: 'Target hosts for Ansible (e.g., "user@host")'
required: true
SERVER_PORT:
description: 'Target port for Ansible (e.g., "22")'
required: false
default: '22'
SERVER_USER:
description: 'Target user for Ansible (e.g., "cicd")'
required: false
default: 'cicd'
# SSH private key to connect to the Ansible target host.
SERVER_SSH_PRIVATE_KEY:
description: 'SSH Private Key for Ansible'
required: false
# Password for the target host.
SERVER_PASSWORD:
description: 'Password for Ansible'
required: false
# Password for the Ansible vault.
VAULT_PASSWORD:
description: 'Ansible Vault Password'
required: true
# Docker registry username.
DOCKER_USERNAME:
description: 'Docker Registry Username'
required: true
# Docker registry password or token.
DOCKER_PASSWORD:
description: 'Docker Registry Password'
required: true
# The URL of the Docker registry.
DOCKER_REGISTRY:
description: 'Docker Registry URL'
required: true
jobs:
# ============================================================================
# JOB 1: BUILD AND PUSH DOCKER IMAGE
# ============================================================================
build:
runs-on: ubuntu-latest
outputs:
image_tag: ${{ steps.meta.outputs.version }}
image_name: ${{ secrets.DOCKER_REGISTRY }}/${{ github.repository_owner }}/${{ inputs.app_name }}
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Registry
uses: docker/login-action@v3
with:
registry: ${{ secrets.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKER_REGISTRY }}/${{ github.repository_owner }}/${{ inputs.app_name }}
tags: |
type=sha,prefix=,format=long
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# ============================================================================
# JOB 2: DEPLOY WITH ANSIBLE
# ============================================================================
deploy:
runs-on: ubuntu-latest
needs: build # This job runs only after the 'build' job succeeds.
steps:
- name: Checkout Application Code
uses: actions/checkout@v4
- name: Checkout Ansible Repo
uses: actions/checkout@v4
with:
# This should be the path to your ansible-gitea repository
repository: ${{ github.repository_owner }}/ansible-gitea
path: .ansible
- name: Set up Ansible and Dependencies
run: |
sudo apt-get update
sudo apt-get install -y sshpass python3-pip
pip3 install ansible
ansible-galaxy collection install community.docker
- name: Setup SSH
if: ${{ secrets.SERVER_SSH_PRIVATE_KEY != '' }}
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SERVER_SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
# Disable strict host key checking for simplicity in CI
echo "StrictHostKeyChecking no" >> ~/.ssh/config
- name: Setup Ansible Vault
run: |
echo "${{ secrets.VAULT_PASSWORD }}" > .ansible/.vault_pass
- name: Run Ansible Playbook
working-directory: .ansible
env:
SERVER_HOST: ${{ secrets.SERVER_HOST }}
SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }}
SERVER_SSH_PRIVATE_KEY: ${{ secrets.SERVER_SSH_PRIVATE_KEY }}
run: |
auth_method_var=""
if [[ -n "$SERVER_SSH_PRIVATE_KEY" ]]; then
auth_method_var="auth_method=ssh_key ssh_key_path=/root/.ssh/id_rsa"
if [[ -n "$SERVER_PASSWORD" ]]; then
auth_method_var="$auth_method_var server_become_password=$SERVER_PASSWORD"
fi
elif [[ -n "$SERVER_PASSWORD" ]]; then
auth_method_var="auth_method=password server_password=$SERVER_PASSWORD"
auth_method_var="$auth_method_var server_become_password=$SERVER_PASSWORD"
fi
ansible-playbook playbook.yml \
-i "$SERVER_HOST," \
--extra-vars "app_name=${{ inputs.app_name }} \
deploy_env=${{ inputs.deploy_env }} \
server_user=${{ inputs.server_user }} \
server_port=${{ inputs.server_port }} \
server_host=$SERVER_HOST \
git_repo=${{ github.server_url }}/${{ github.repository }} \
docker_image_tag=${{ needs.build.outputs.image_tag }} \
docker_image_name=${{ needs.build.outputs.image_name }} \
docker_image_full=${{ needs.build.outputs.image_name }}:${{ needs.build.outputs.image_tag }} \
docker_registry=${{ secrets.DOCKER_REGISTRY }} \
$auth_method_var" \
--vault-password-file .vault_pass
+36
View File
@@ -0,0 +1,36 @@
# This is the main CI/CD workflow for the cms-api application.
# It triggers on pushes of tags matching semantic versioning with environment suffixes.
name: CI/CD for project
on:
push:
tags:
- 'v*' # Matches v-prefixed tags like v1.0.0, v1.0.0-dev.1, v1.0.0-stg.1
jobs:
# This job calls the reusable deployment workflow.
deploy_service:
# Set the environment name based on the Git tag
environment: ${{ (contains(github.ref_name, '-dev.')) && 'development' || (contains(github.ref_name, '-stg.')) && 'staging' || 'production' }}
uses: devops/ansible-gitea/.gitea/workflows/deploy.yml@main
# Provide the specific inputs for this application.
with:
app_name: ${{ github.event.repository.name }}
deploy_env: ${{ (contains(github.ref_name, '-dev.')) && 'development' || (contains(github.ref_name, '-stg.')) && 'staging' || 'production' }}
env_type: ${{ (contains(github.ref_name, 'worker')) && 'worker' || '' }}
# Pass the required secrets to the reusable workflow.
# These secrets must be configured in the Gitea settings for this repository.
secrets:
SERVER_HOST: ${{ secrets.SERVER_HOST }}
SERVER_PORT: ${{ secrets.SERVER_PORT }}
SERVER_USER: ${{ secrets.SERVER_USER }}
SERVER_SSH_PRIVATE_KEY: ${{ secrets.SERVER_SSH_PRIVATE_KEY }}
SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }}
VAULT_PASSWORD: ${{ secrets.VAULT_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_REGISTRY: ${{ secrets.DOCKER_REGISTRY }}
+21
View File
@@ -0,0 +1,21 @@
# Ansible
*.retry
.vault_pass
# IDE
.vscode/
.idea/
# OS
.DS_Store
Thumbs.db
# Logs
*.log
# Local testing
vagrant/
.vagrant/
# Temporary files
/tmp/ansible_ssh_key
+9
View File
@@ -0,0 +1,9 @@
[defaults]
host_key_checking = False
timeout = 30
deprecation_warnings = False
interpreter_python = auto_silent
[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no
+109
View File
@@ -0,0 +1,109 @@
---
port_registry_file: "group_vars/port-registry.yml"
# Thông tin triển khai
deploy_env: production # Môi trường triển khai: development hoặc production
app_name: "App" # Tên ứng dụng (sẽ được ghi đè)
app_port: 3000 # Port mặc định
node_env: "{{ deploy_env }}" # NODE_ENV
nodejs_version: "18" # Phiên bản Node.js mặc định
framework: expressjs # Framework (sẽ được ghi đè): expressjs, nestjs, nextjs, reactjs
# Thông tin Git
git_repo: "" # Repo URL (bắt buộc ghi đè)
git_tag: "latest" # Tag/branch để checkout
git_username: "" # Username để clone repo (nếu private)
git_token: "" # Token để clone repo (nếu private)
# Thông tin triển khai
deploy_strategy: "docker" # Phương thức triển khai (pm2, systemd, etc)
app_base_dir: "/var/www" # Thư mục cơ sở
app_dir: "{{ app_base_dir }}/{{ app_name }}-{{ deploy_env }}{{ env_tag }}"
deploy_dir: "{{ app_dir }}-{{ git_tag }}"
current_dir: "{{ app_dir }}-current"
keep_releases: 3 # Số lượng phiên bản cần giữ lại
# Các lệnh ứng dụng
app_install_cmd: "npm ci" # Lệnh cài đặt dependencies
app_build_cmd: "npm run build" # Lệnh build ứng dụng
app_start_cmd: "" # Lệnh khởi động ứng dụng
app_start_cmds: # Lệnh khởi động ứng dụng theo framework
expressjs: "./dist/main.js"
nestjs: "./dist/main.js"
reactjs: "serve"
nextjs: "node_modules/next/dist/bin/next"
app_start_args: # Tham so cho lệnh khởi động ứng dụng
expressjs: ""
nestjs: ""
reactjs: "-s build"
nextjs: "start"
#app_start_cmd_override_worker: "./dist/cli.js sync_queue" #override start command for worker
# Thông tin server
server_host: "" # Server IP/hostname (bắt buộc ghi đè)
server_port: "22" # Server Port
server_user: "" # Username SSH (bắt buộc ghi đè)
server_password: "" # Password SSH (để trống nếu dùng SSH key)
server_become_password: "" # Password sudo (để trống nếu dùng SSH key)
ssh_key_path: "" # Đường dẫn SSH private key file (để trống nếu dùng ssh_key_content)
ssh_key_content: "" # Nội dung SSH private key (từ GitLab CI/CD variables)
auth_method: "password" # Phương thức xác thực: "password" hoặc "ssh_key"
# Environment-specific overrides
environment_config:
development:
short_key: "dev"
log_level: "debug"
enable_swagger: true
enable_hot_reload: true
database_logging: true
pm2_instances: 1
pm2_max_memory: "1G"
production:
short_key: "prod"
log_level: "warn"
enable_swagger: false
enable_hot_reload: false
database_logging: false
pm2_instances: 1
pm2_max_memory: "2G"
# Path Configurations
entities_dir: "src/api/models"
migrations_dir: "src/database/migrations"
seeding_factories: "src/database/factories/**/*{.ts,.js}"
seeding_seeds: "src/database/seeds/**/*{.ts,.js}"
controllers_dir: "/api/controllers/**/*Controller{.ts,.js}"
cron_dir: "/api/cron-jobs/**/*Job{.ts,.js}"
middlewares_dir: "/infrastructure/middlewares/**/*{.ts,.js}"
subscribers_dir: "/api/subscribers/**/*Subscriber{.ts,.js}"
events_dir: "/api/events/**/*{.ts,.js}"
resolvers_dir: "/api/resolvers/**/*Resolver{.ts,.js}"
# Health Check Configuration
health_check:
enabled: false
endpoint: ""
timeout: 5000
interval: 30000
# Security Configuration
security:
cors_origins: "*"
helmet_enabled: true
rate_limit_enabled: true
request_size_limit: "50mb"
trust_proxy: true
# Custom NPM Scripts
custom_scripts:
enabled: false
# Deployment Configuration
pm2_restart_delay: 5000
# Auto-scaling configuration
replicas: 1
max_replicas: 9
health_check_timeout: 30
deployment_timeout: 300
+11
View File
@@ -0,0 +1,11 @@
# port-registry.yml
projects:
tarot-fe:
development: 3002
production: 3002
staging: 13002
tarot-be:
development: 3003
production: 3003
staging: 13003
+6
View File
@@ -0,0 +1,6 @@
---
# default resource_limits
resource_limits:
development:
memory: "2g"
cpu: "1.0"
+6
View File
@@ -0,0 +1,6 @@
---
# default resource_limits
resource_limits:
production:
memory: "4g"
cpu: "2.0"
+14
View File
@@ -0,0 +1,14 @@
all:
hosts:
dev_server:
ansible_host: "{{ server_host if server_host is defined and server_host != '' else '10.252.231.175' }}"
ansible_port: "{{ server_port if server_port is defined and server_port != '' else '22' }}"
ansible_user: "{{ server_user }}"
# Cấu hình SSH linh động - password hoặc SSH key
ansible_password: "{{ server_password if auth_method == 'password' else omit }}"
ansible_ssh_private_key_file: "{{ ssh_key_path if (auth_method == 'ssh_key' and ssh_key_path != '') else omit }}"
ansible_become_password: "{{ server_become_password if auth_method == 'password' else omit }}"
ansible_sudo_pass: "{{ server_become_password if auth_method == 'password' else omit }}"
vars:
# Load vault variables
# vault_file: "defaults/vault/development.yml"
+14
View File
@@ -0,0 +1,14 @@
all:
hosts:
prod_server:
ansible_host: "{{ server_host if server_host is defined and server_host != '' else '10.252.252.10' }}"
ansible_port: "{{ server_port if server_port is defined and server_port != '' else '22' }}"
ansible_user: "{{ server_user }}"
# Cấu hình SSH linh động - password hoặc SSH key
ansible_password: "{{ server_password if auth_method == 'password' else omit }}"
ansible_ssh_private_key_file: "{{ ssh_key_path if (auth_method == 'ssh_key' and ssh_key_path != '') else omit }}"
ansible_become_password: "{{ server_become_password if auth_method == 'password' else omit }}"
ansible_sudo_pass: "{{ server_become_password if auth_method == 'password' else omit }}"
vars:
# Load vault variables
# vault_file: "defaults/vault/production.yml"
+136
View File
@@ -0,0 +1,136 @@
---
- name: Deploy Application with Auto-Scaling
hosts: "{{ target_hosts | default('all') }}"
become: yes
become_method: sudo
vars_files:
- group_vars/default.yml
- "group_vars/vault/{{ deploy_env }}.yml"
vars:
# Deployment configuration
deploy_strategy: "{{ deploy_strategy | default('docker') }}"
docker_image_tag: "{{ docker_image_tag | default('latest') }}"
# Environment detection
is_production: "{{ deploy_env == 'production' }}"
is_staging: "{{ deploy_env == 'staging' }}"
is_development: "{{ deploy_env == 'development' }}"
pre_tasks:
- name: Validate required variables
assert:
that:
- app_name is defined and app_name != ""
- git_repo is defined and git_repo != ""
- server_user is defined and server_user != ""
- deploy_env is defined and deploy_env != ""
fail_msg: "Missing required variables: app_name, git_repo, server_user, and deploy_env must be defined"
- name: Validate auto-scaling configuration
assert:
that:
- replicas | int >= 1
- replicas | int <= max_replicas | int
- deployment_strategy in ['rolling', 'blue-green', 'recreate']
fail_msg: "Invalid auto-scaling configuration. Replicas must be between 1 and {{ max_replicas }}, and deployment_strategy must be one of: rolling, blue-green, recreate"
- name: Load project-specific variables
include_vars: "projects/{{ app_name }}/vars.yml"
when:
- lookup('file', 'projects/' + app_name + '/vars.yml', errors='ignore')
- name: Override deployment strategy from project vars
set_fact:
deploy_strategy: "{{ deploy_strategy | default('docker') }}"
- name: Ensure port registry exists
file:
path: "{{ port_registry_file }}"
state: touch
delegate_to: localhost
run_once: true
- name: Initialize port registry if empty
copy:
content: "projects: {}"
dest: "{{ port_registry_file }}"
force: no
delegate_to: localhost
run_once: true
tasks:
# ============================================================================
# DOCKER DEPLOYMENT WITH AUTO-SCALING
# ============================================================================
- name: Include docker role with auto-scaling
include_role:
name: docker
when: deploy_strategy in ['simple-docker', 'docker']
# ============================================================================
# POST-DEPLOYMENT VERIFICATION
# ============================================================================
- name: Wait for deployment to settle
pause:
seconds: 10
when: deploy_strategy in ['simple-docker', 'docker']
# ============================================================================
# CLEANUP AND MAINTENANCE
# ============================================================================
- name: Cleanup old Docker images
shell: |
sudo /home/{{ server_user }}/scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh clean
become_user: "{{ server_user }}"
when:
- deploy_strategy in ['simple-docker', 'docker']
- cleanup_old_images | default(true)
ignore_errors: yes
# ============================================================================
# DEPLOYMENT SUMMARY
# ============================================================================
- name: Generate deployment summary
set_fact:
deployment_summary:
application: "{{ app_name }}"
environment: "{{ deploy_env }}{{ env_tag | default('') }}"
version: "{{ docker_image_tag }}"
replicas: "{{ replicas }}"
strategy: "{{ deployment_strategy }}"
deploy_strategy: "{{ deploy_strategy }}"
timestamp: "{{ ansible_date_time.iso8601 }}"
duration: "{{ ansible_play_duration | default('unknown') }}"
- name: Display deployment summary
debug:
msg:
- "🎉 DEPLOYMENT SUMMARY"
- "===================="
- "Application: {{ deployment_summary.application }}"
- "Environment: {{ deployment_summary.environment }}"
- "Version: {{ deployment_summary.version }}"
- "Replicas: {{ deployment_summary.replicas }}"
- "Strategy: {{ deployment_summary.strategy }}"
- "Timestamp: {{ deployment_summary.timestamp }}"
- name: Save deployment record
copy:
content: "{{ deployment_summary | to_nice_yaml }}"
dest: "/home/{{ server_user }}/ci/logs/{{ app_name }}/{{ deploy_env }}/deployment-{{ ansible_date_time.epoch }}.yml"
when: save_deployment_record | default(true)
# ============================================================================
# HANDLERS
# ============================================================================
handlers:
- name: reload nginx
service:
name: nginx
state: reloaded
when: ansible_service_mgr == "systemd"
# ============================================================================
# POST TASKS
# ============================================================================
+19
View File
@@ -0,0 +1,19 @@
---
# Docker configuration
service_name: "{{ app_name }}-{{ deploy_env }}{{ env_tag }}"
#docker_registry: "{{ ansible_env.CI_REGISTRY | default('gitlab.forlife.one') }}"
#docker_project_path: "{{ ansible_env.CI_PROJECT_PATH | default('') }}"
#docker_image_tag: "{{ ansible_env.CI_COMMIT_TAG | default('latest') }}"
#docker_image_full: "{{ ansible_env.CI_REGISTRY_IMAGE }}:{ ansible_env.CI_COMMIT_TAG | default(docker_image_tag) }"
#docker_image_name: "{{ ansible_env.CI_REGISTRY_IMAGE }}"
docker_image_rollback: "{{ app_name }}:{{ previous_version.stdout | default('latest') }}"
# Directory configuration
deploy_dir: "{{ '/home/' + server_user + '/ci/deployments/' + app_name + '/' + deploy_env }}"
current_dir: "{{ '/home/' + server_user + '/ci/deployments/' + app_name + '/current' }}"
# GitLab CI integration
#gitlab_ci_user: "{{ ansible_env.CI_REGISTRY_USER | default('gitlab-ci-token') }}"
# Health Check Configuration
health_path: ""
+86
View File
@@ -0,0 +1,86 @@
---
# ============================================================================
# NGINX HANDLERS FOR AUTO-SCALING DEPLOYMENT
# ============================================================================
# if nginx install by aaPanel: run command > mkdir -p /etc/nginx/conf.d/
# if nginx install by aaPanel: run command > ln -fs /www/server/nginx/sbin/nginx /usr/bin/nginx
# if nginx install by aaPanel: run command > echo "map $http_upgrade $connection_upgrade { default upgrade; '' close; }" > /www/server/panel/vhost/nginx/0.websocket.conf
# if nginx install by aaPanel: run command > echo "include /etc/nginx/conf.d/*.conf;" > /www/server/panel/vhost/nginx/forlife-upstream.conf
- name: test nginx configuration
command: nginx -t
become: yes
listen: test nginx configuration
- name: reload nginx
shell: |
# Test nginx configuration
nginx -t && /etc/init.d/nginx reload
# Wait for reload
sleep 3
become: yes
when: ansible_service_mgr == "systemd"
listen: reload nginx
- name: webapplication purge cache
shell: |
filepath="/www/server/scripts/cloudflare-purge-cache.sh"
if [ -f "$filepath" ]; then bash "$filepath"; fi
become: yes
when: ansible_service_mgr == "systemd"
listen: webapplication purge cache
- name: restart nginx
shell: |
# Test nginx configuration
nginx -t && /etc/init.d/nginx restart
# Wait for reload
sleep 3
become: yes
when: ansible_service_mgr == "systemd"
listen: restart nginx
- name: validate nginx upstream
shell: |
# Test nginx configuration
nginx -t && /etc/init.d/nginx reload
# Wait for reload
sleep 3
become: yes
ignore_errors: true
listen: validate nginx upstream
# service:
# name: nginx
# state: reloaded
# become: yes
# when: ansible_service_mgr == "systemd"
# listen: reload nginx
#- name: restart nginx
# service:
# name: nginx
# state: restarted
# become: yes
# when: ansible_service_mgr == "systemd"
# listen: restart nginx
#- name: validate nginx upstream
# shell: |
# # Test nginx configuration
# nginx -t && nginx -s reload
#
# # Wait for reload
# sleep 3
#
# # Test upstream connectivity
# if command -v curl >/dev/null 2>&1; then
# echo "Testing upstream connectivity..."
# curl -f -s --max-time 10 http://localhost/health || echo "⚠️ Upstream health check skipped"
# fi
# become: yes
# ignore_errors: true
# listen: validate nginx upstream
+650
View File
@@ -0,0 +1,650 @@
---
# ============================================================================
# ENHANCED DOCKER DEPLOYMENT WITH AUTO-SCALING SUPPORT
# ============================================================================
# ============================================================================
# SYSTEM VALIDATION AND REQUIREMENTS CHECK
# ============================================================================
- name: Validate deployment environment
debug:
msg:
- "=== AUTO-SCALING DEPLOYMENT VALIDATION ==="
- "Target OS: {{ ansible_distribution }} {{ ansible_distribution_version }}"
- "Architecture: {{ ansible_architecture }}"
- "Available memory: {{ ansible_memtotal_mb }}MB"
- "Available disk: {{ ansible_mounts[0].size_available / 1024 / 1024 / 1024 | round(2) }}GB"
- "Target replicas: {{ docker_replicas | default(replicas | default(1)) }}"
- "Deployment strategy: {{ docker_deployment_strategy | default(deployment_strategy | default('rolling')) }}"
- name: Check minimum system requirements for auto-scaling
assert:
that:
- ansible_memtotal_mb >= (docker_replicas | default(1) | int * 512)
- (ansible_mounts[0].size_available / 1024 / 1024 / 1024) >= 5
- docker_replicas | default(1) | int <= docker_max_replicas | default(9) | int
fail_msg: |
Insufficient system resources for {{ docker_replicas | default(1) }} replicas.
Need at least {{ docker_replicas | default(1) | int * 512 }}MB RAM and 5GB disk space.
Replicas ({{ docker_replicas | default(1) }}) cannot exceed max_replicas ({{ docker_max_replicas | default(9) }}).
success_msg: "✅ System requirements met for auto-scaling deployment."
# ============================================================================
# PROJECT-SPECIFIC CONFIGURATION LOADING
# ============================================================================
- name: Load project-specific vars if exists
block:
- name: Check if project-specific variables file exists
stat:
path: "projects/{{ app_name }}/vars.yml"
register: project_vars_file
- name: Load project-specific variables
include_vars:
file: "projects/{{ app_name }}/vars.yml"
when: project_vars_file.stat.exists
- name: Check if project-specific variables by environment file exists
stat:
path: "projects/{{ app_name }}/group_vars/{{ deploy_env }}.yml"
register: project_vars_env_file
- name: Load project-specific variables by environment
include_vars:
file: "projects/{{ app_name }}/group_vars/{{ deploy_env }}.yml"
when: project_vars_env_file.stat.exists
delegate_to: localhost
run_once: true
- name: Load port registry
include_vars:
file: "{{ port_registry_file | default('group_vars/port_registry.yml') }}"
name: port_registry
delegate_to: localhost
run_once: true
when: port_registry_file is defined
# ============================================================================
# DEPLOYMENT CONFIGURATION SETUP
# ============================================================================
- name: Set deployment environment tag
set_fact:
deploy_env_tag: "{{ deploy_env }}{{ env_tag | default('') }}"
- name: Set auto-scaling configuration
set_fact:
# Auto-scaling parameters
final_replicas: "{{ docker_replicas | default(replicas | default(1)) }}"
final_max_replicas: "{{ docker_max_replicas | default(max_replicas | default(9)) }}"
final_deployment_strategy: "{{ docker_deployment_strategy | default(deployment_strategy | default('rolling')) }}"
final_health_check_timeout: "{{ docker_health_check_timeout | default(health_check_timeout | default(30)) }}"
final_deployment_timeout: "{{ docker_deployment_timeout | default(deployment_timeout | default(300)) }}"
# Port configuration
final_app_port: "{{ port_registry.projects[app_name][deploy_env_tag] | default(3000) }}"
final_container_port: "{{ container_port | default(3000) }}"
# Resource configuration
final_memory_limit: "{{ container_memory_limit | default(resource_limits[deploy_env].memory | default('1g')) }}"
final_cpu_limit: "{{ container_cpu_limit | default(resource_limits[deploy_env].cpu | default('1.0')) }}"
# Network configuration
final_docker_network: "{{ docker_network_name | default(app_name + '-' + deploy_env + '-network') }}"
final_restart_policy: "{{ container_restart_policy | default('unless-stopped') }}"
- name: Display auto-scaling deployment configuration
debug:
msg:
- "🚀 AUTO-SCALING DEPLOYMENT CONFIGURATION"
- "========================================"
- "Application: {{ app_name }}"
- "Environment: {{ deploy_env_tag }}"
- "Framework: {{ framework | default('unknown') }}"
- "Docker Image: {{ docker_image_name }}:{{ docker_image_tag }}"
- "Target Replicas: {{ final_replicas }}"
- "Max Replicas: {{ final_max_replicas }}"
- "Deployment Strategy: {{ final_deployment_strategy }}"
- "Base Port: {{ final_app_port }} (Pattern: {{ final_app_port }}{{ replica_number | default(1) }})"
- "Container Port: {{ final_container_port }}"
- "Memory Limit: {{ final_memory_limit }}"
- "CPU Limit: {{ final_cpu_limit }}"
- "Health Check Timeout: {{ final_health_check_timeout }}s"
- "Deployment Timeout: {{ final_deployment_timeout }}s"
# ============================================================================
# PRE-DEPLOYMENT VALIDATION
# ============================================================================
- name: Validate auto-scaling deployment parameters
assert:
that:
- app_name is defined and app_name != ""
- deploy_env in ['development', 'staging', 'production']
- docker_image_tag is defined and docker_image_tag != ""
- final_replicas | int >= 1
- final_replicas | int <= final_max_replicas | int
- final_deployment_strategy in ['rolling', 'blue-green', 'recreate']
- docker_registry is defined
fail_msg: "Missing or invalid auto-scaling deployment parameters"
success_msg: "✅ Auto-scaling deployment parameters validated"
tags: [validate]
# ============================================================================
# DIRECTORY AND ENVIRONMENT SETUP
# ============================================================================
- name: Create deployment directories
file:
path: "{{ item }}"
state: directory
owner: "{{ server_user }}"
group: "{{ server_user }}"
mode: '0755'
loop:
- "/home/{{ server_user }}/scripts"
- "/home/{{ server_user }}/ci"
- "/home/{{ server_user }}/ci/configs/{{ app_name }}/{{ deploy_env }}"
- "/home/{{ server_user }}/ci/data/{{ app_name }}/{{ deploy_env }}"
- "/home/{{ server_user }}/ci/logs/{{ app_name }}/{{ deploy_env }}"
- "/home/{{ server_user }}/ci/backups/{{ app_name }}/{{ deploy_env }}"
- "/home/{{ server_user }}/ci/deployments/{{ app_name }}/{{ deploy_env }}"
- name: Create environment file from vault if secrets_dict is defined
copy:
dest: "/home/{{ server_user }}/ci/configs/{{ app_name }}/{{ deploy_env }}/.env"
content: |
{% for key, value in secrets_dict.items() %}
{{ key }}={{ value }}
{% endfor %}
when: secrets_dict is defined
- name: Load project-specific env if exists
block:
- name: Check if project-specific .env template exists by environment
stat:
path: "projects/{{ app_name }}/templates/.env.{{ deploy_env }}.j2"
register: custom_env_template_by_env
delegate_to: localhost
run_once: true
- name: Create environment file from project template by environment
template:
src: "projects/{{ app_name }}/templates/.env.{{ deploy_env }}.j2"
dest: "/home/{{ server_user }}/ci/configs/{{ app_name }}/{{ deploy_env }}/.env"
owner: "{{ server_user }}"
group: "{{ server_user }}"
mode: '0600'
backup: yes
when: env_file_enabled | default(true) and custom_env_template_by_env.stat.exists
- name: Check if project-specific .env template exists
stat:
path: "projects/{{ app_name }}/templates/.env.j2"
register: custom_env_template
delegate_to: localhost
run_once: true
- name: Create environment file from project template
template:
src: "projects/{{ app_name }}/templates/.env.j2"
dest: "/home/{{ server_user }}/ci/configs/{{ app_name }}/{{ deploy_env }}/.env"
owner: "{{ server_user }}"
group: "{{ server_user }}"
mode: '0600'
backup: yes
when: env_file_enabled | default(true) and custom_env_template.stat.exists and not custom_env_template_by_env.stat.exists
- name: Create environment file with default template
template:
src: .env.j2
dest: "/home/{{ server_user }}/ci/configs/{{ app_name }}/{{ deploy_env }}/.env"
owner: "{{ server_user }}"
group: "{{ server_user }}"
mode: '0600'
backup: yes
when: not custom_env_template_by_env.stat.exists and not custom_env_template.stat.exists
vars:
# Pass auto-scaling configuration to template
auto_scaling_config:
replicas: "{{ final_replicas }}"
max_replicas: "{{ final_max_replicas }}"
deployment_strategy: "{{ final_deployment_strategy }}"
base_port: "{{ final_app_port }}"
container_port: "{{ final_container_port }}"
tags: [environment]
when: secrets_dict is not defined
# ============================================================================
# DOCKER ENVIRONMENT SETUP
# ============================================================================
- name: Setup Docker environment for auto-scaling
block:
- name: Check if Docker is installed
shell: |
command -v docker
register: docker_check
failed_when: false
changed_when: false
- name: Install Docker (Debian/Ubuntu)
apt:
name: docker.io
state: present
update_cache: yes
become: yes
when:
- docker_check.rc != 0
- ansible_os_family == "Debian"
- name: Install Docker (CentOS/RHEL)
yum:
name: docker
state: present
become: yes
when:
- docker_check.rc != 0
- ansible_os_family == "RedHat"
# Kiểm tra xem Python đã có Docker SDK chưa
- name: Check if Docker SDK for Python is installed
command: python3 -c "import docker"
register: docker_sdk_check
failed_when: false
changed_when: false
# Cài Docker SDK bằng pip nếu chưa có
- name: Install Docker SDK for Python via apt (nếu không muốn dùng pip)
apt:
name: python3-docker
state: present
become: yes
when:
- ansible_os_family == "Debian"
- docker_sdk_check.rc != 0
- name: Ensure Docker service is started and enabled
service:
name: docker
state: started
enabled: true
# - name: Create Docker network for auto-scaling deployment
# docker_network:
# name: "{{ final_docker_network }}"
# state: present
# driver: bridge
# ipam_config:
# - subnet: "172.{{ (final_app_port | string)[-2:] | int + 20 }}.0.0/16"
- name: Gather Docker networks on host
shell: docker network inspect $(docker network ls -q) 2>/dev/null || echo '[]'
register: docker_networks_raw
changed_when: false
failed_when: false
- name: Parse Docker networks JSON
set_fact:
docker_networks_info: "{{ docker_networks_raw.stdout | from_json }}"
- name: Extract existing subnets (as strings) from docker networks
set_fact:
existing_subnets: >-
{{
docker_networks_info | default([])
| selectattr('IPAM', 'defined')
| selectattr('IPAM.Config', 'defined')
| map(attribute='IPAM.Config')
| select
| map('first')
| selectattr('Subnet', 'defined')
| map(attribute='Subnet')
| list
}}
- name: Debug existing subnets
debug:
var: existing_subnets
- name: Get used second-octets from existing subnets (172.X.0.0/16 only)
set_fact:
used_seconds: >-
{%- set result = [] -%}
{%- for subnet in existing_subnets -%}
{%- if subnet.startswith('172.') -%}
{%- set parts = subnet.split('.') -%}
{%- if parts | length >= 2 -%}
{%- set _ = result.append(parts[1] | int) -%}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{{ result }}
- name: Debug used second octets
debug:
var: used_seconds
- name: Compute start octet from final_app_port (bounded 20..250)
set_fact:
start_octet: >-
{{
(((final_app_port | string)[-2:] | int) + 20)
if (((final_app_port | string)[-2:] | int) + 20) <= 250
else 250
}}
- name: Build candidate second-octet list (start -> 250 then 20 -> start-1)
set_fact:
candidate_list: >-
{{
(range(start_octet | int, 251) | list) + (range(20, start_octet | int) | list)
}}
- name: Debug candidate list
debug:
msg: "Candidates: {{ candidate_list }}, Used: {{ used_seconds }}"
- name: Select first unused second-octet
set_fact:
chosen_second: >-
{{
candidate_list
| map('int')
| reject('in', used_seconds)
| list
| first
}}
- name: Fail if couldn't find free subnet octet
fail:
msg: "No available 172.X.0.0/16 subnet found in candidate space (20..250). Existing used: {{ used_seconds }}"
when: chosen_second is not defined
- name: Build chosen subnet
set_fact:
chosen_subnet: "172.{{ chosen_second }}.0.0/16"
- name: Debug chosen subnet
debug:
msg: "Creating Docker network '{{ final_docker_network }}' with subnet {{ chosen_subnet }}"
- name: Create Docker network for auto-scaling deployment
community.docker.docker_network:
name: "{{ final_docker_network }}"
state: present
driver: bridge
ipam_config:
- subnet: "{{ chosen_subnet }}"
become: true
- name: Pull Docker image for auto-scaling deployment
docker_image:
name: "{{ docker_image_full | default(docker_image_name + ':' + docker_image_tag) }}"
source: pull
force_source: true
register: docker_pull_result
tags: [docker]
# ============================================================================
# ENHANCED DOCKER MANAGEMENT SCRIPT GENERATION
# ============================================================================
- name: Generate enhanced Docker management script with auto-scaling
template:
src: manage-docker.sh.j2
dest: "/home/{{ server_user }}/scripts/manage-{{ service_name }}.sh"
owner: "{{ server_user }}"
group: "{{ server_user }}"
mode: '0755'
backup: yes
vars:
# Container configuration
server_user: "{{ server_user }}"
container_name: "{{ service_name }}"
framework: "{{ framework }}"
# Auto-scaling configuration
default_replicas: "{{ final_replicas }}"
max_replicas: "{{ final_max_replicas }}"
health_check_timeout: "{{ final_health_check_timeout }}"
deployment_timeout: "{{ final_deployment_timeout }}"
# Port configuration
app_port: "{{ final_app_port }}"
container_port: "{{ final_container_port }}"
# Resource configuration
container_restart_policy: "{{ final_restart_policy }}"
container_memory_limit: "{{ final_memory_limit }}"
container_cpu_limit: "{{ final_cpu_limit }}"
# Network configuration
docker_network_name: "{{ final_docker_network }}"
# ============================================================================
# AUTO-SCALING DEPLOYMENT EXECUTION
# ============================================================================
- name: Check current deployment state
shell: |
if [ -f "/home/{{ server_user }}/scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh" ]; then
sudo /home/{{ server_user }}/scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh status 2>/dev/null || echo "no-containers"
else
echo "no-script"
fi
become_user: "{{ server_user }}"
register: current_deployment_state
changed_when: false
- name: Display current deployment state
debug:
msg: "{{ current_deployment_state.stdout_lines }}"
when: current_deployment_state.stdout_lines is defined
- name: Ensure Docker is installed and user has permissions
block:
- name: Add user to docker group
user:
name: "{{ server_user }}"
groups: docker
append: yes
become: yes
- name: Reset SSH connection for group changes
meta: reset_connection
- name: Test Docker access
command: docker version
register: docker_test
failed_when: docker_test.rc != 0
- name: Execute smart deployment with auto-scaling
shell: |
sudo /home/{{ server_user }}/scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh deploy {{ replicas | default(1) }} {{ docker_image_tag }} rolling
async: 600
poll: 10 # Poll every 10 seconds instead of 0
register: deployment_result
become_user: "{{ server_user }}"
- name: Check deployment result
fail:
msg: "Deployment failed: {{ deployment_result.stderr | default('Unknown error') }}"
when:
- deployment_result.rc is defined
- deployment_result.rc != 0
# ============================================================================
# POST-DEPLOYMENT VERIFICATION
# ============================================================================
- name: Wait for auto-scaling deployment to settle
pause:
seconds: 5
tags: [health]
- name: Verify auto-scaling deployment completion
shell: |
sudo /home/{{ server_user }}/scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh health
become_user: "{{ server_user }}"
register: auto_scaling_health_check
retries: 5
delay: 15
until: auto_scaling_health_check.rc == 0
tags: [health]
- name: Get final auto-scaling deployment status
shell: |
sudo /home/{{ server_user }}/scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh status
become_user: "{{ server_user }}"
register: final_auto_scaling_status
changed_when: false
tags: [health]
- name: Extract auto-scaling deployment metrics
set_fact:
auto_scaling_metrics:
running_containers: "{{ final_auto_scaling_status.stdout | regex_search('Running Containers: (\\d+)', '\\1') | first | default('0') }}"
current_version: "{{ final_auto_scaling_status.stdout | regex_search('Current Version: ([^\\n]+)', '\\1') | first | default('unknown') }}"
base_port: "{{ final_auto_scaling_status.stdout | regex_search('Base Port: (\\d+)', '\\1') | first | default('0') }}"
# - name: Validate auto-scaling deployment metrics
# assert:
# that:
# - auto_scaling_metrics.running_containers | int == final_replicas | int
# - auto_scaling_metrics.current_version == docker_image_tag
# fail_msg: |
# Auto-scaling deployment validation failed:
# Expected: {{ final_replicas }} replicas with version {{ docker_image_tag }}
# Actual: {{ auto_scaling_metrics.running_containers }} replicas with version {{ auto_scaling_metrics.current_version }}
# success_msg: "✅ Auto-scaling deployment validation passed"
# ============================================================================
# LOAD BALANCER INTEGRATION
# ============================================================================
- name: Get active ports from script
shell: |
sudo /home/{{ server_user }}/scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh deploy_active_ports
become_user: "{{ server_user }}"
register: active_ports_result
when: env_tag | default('') != '-worker'
- name: Convert ports to clean list
set_fact:
active_ports_list: "{{ active_ports_result.stdout.split() }}"
when: env_tag | default('') != '-worker' and active_ports_result.stdout | length > 0
- name: Generate nginx upstream configuration
template:
src: nginx-upstream.conf.j2
dest: "/etc/nginx/conf.d/{{ app_name }}-{{ deploy_env }}-upstream.conf"
backup: yes
vars:
upstream_name: "{{ app_name | regex_replace('[^A-Za-z0-9_]', '_') }}_{{ deploy_env }}_backend"
base_port: "{{ final_app_port }}"
replica_count: "{{ final_replicas }}"
load_balancing_method: "{{ nginx_config.load_balancing_method | default('least_conn') }}"
max_fails: "{{ nginx_config.max_fails | default(1) }}"
fail_timeout: "{{ nginx_config.fail_timeout | default(10) }}"
active_ports: "{{ active_ports_list }}"
when:
- update_load_balancer | default(true)
- env_tag | default('') != '-worker'
notify:
- test nginx configuration
- reload nginx
- webapplication purge cache
tags: [loadbalancer]
# ============================================================================
# DEPLOYMENT SUCCESS SUMMARY
# ============================================================================
- name: Display auto-scaling deployment success
debug:
msg:
- "🎉 AUTO-SCALING DEPLOYMENT SUCCESS"
- "=================================="
- "✅ Successfully deployed {{ app_name }} ({{ framework | default('unknown') }})"
- "🔢 Replicas: {{ auto_scaling_metrics.running_containers }}/{{ final_replicas }}"
- "🏷️ Version: {{ auto_scaling_metrics.current_version }}"
- "🚀 Strategy: {{ final_deployment_strategy }}"
- "🌐 Base Port: {{ auto_scaling_metrics.base_port }}"
- "🏥 Health Status: All containers healthy"
- "⚖️ Load Balancer: Updated with {{ final_replicas }} targets"
- ""
- "🔧 Management Commands:"
- " Status: ./scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh status"
- " Scale: ./scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh scale <replicas>"
- " Logs: ./scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh logs"
- " Health: ./scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh health"
tags: [info]
# ============================================================================
# CLEANUP AND MAINTENANCE
# ============================================================================
- name: Cleanup old Docker images (keep last 5 versions)
shell: |
# Enhanced cleanup for auto-scaling deployment
sudo /home/{{ server_user }}/scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh clean
become_user: "{{ server_user }}"
ignore_errors: true
when: cleanup_old_images | default(true)
tags: [cleanup]
# ============================================================================
# DEPLOYMENT RECORD AND AUDIT TRAIL
# ============================================================================
- name: Create auto-scaling deployment record
copy:
content: |
# Auto-Scaling Deployment Record
timestamp: "{{ ansible_date_time.iso8601 }}"
application: "{{ app_name }}"
environment: "{{ deploy_env }}{{ env_tag | default('') }}"
version: "{{ docker_image_tag }}"
replicas: {{ final_replicas }}
max_replicas: {{ final_max_replicas }}
strategy: "{{ final_deployment_strategy }}"
base_port: "{{ final_app_port }}"
framework: "{{ framework | default('unknown') }}"
node_version: "{{ node_version | default('unknown') }}"
status: "success"
duration: "{{ auto_scaling_deployment_result.delta | default('unknown') }}"
operator: "{{ ansible_user_id }}"
git_commit: "{{ git_sha | default('unknown') }}"
git_tag: "{{ git_tag | default('unknown') }}"
memory_limit: "{{ final_memory_limit }}"
cpu_limit: "{{ final_cpu_limit }}"
docker_network: "{{ final_docker_network }}"
health_check_timeout: "{{ final_health_check_timeout }}"
deployment_timeout: "{{ final_deployment_timeout }}"
load_balancer_updated: {{ update_load_balancer | default(true) }}
management_script: "/home/{{ server_user }}/scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh"
dest: "/home/{{ server_user }}/ci/logs/{{ app_name }}/{{ deploy_env }}/deployment-{{ ansible_date_time.epoch }}.yml"
owner: "{{ server_user }}"
group: "{{ server_user }}"
mode: '0644'
tags: [logging]
- name: Register auto-scaling deployment in system log
lineinfile:
path: "/var/log/microservices-deployments.log"
line: "{{ ansible_date_time.iso8601 }} | {{ app_name }} | {{ deploy_env }} | {{ docker_image_tag }} | {{ git_tag }} | AUTO-SCALE-SUCCESS | {{ final_replicas }}@{{ auto_scaling_metrics.base_port }} | {{ framework | default('unknown') }}"
create: true
mode: '0644'
tags: [logging]
- name: Cleanup old backups file
shell: |
ls -1t /home/{{ server_user }}/ci/configs/{{ app_name }}/{{ deploy_env }}/.env.*~ 2>/dev/null | tail -n +4 | xargs -r rm --
ls -1t /home/{{ server_user }}/scripts/manage-{{ service_name }}.sh.*~ 2>/dev/null | tail -n +4 | xargs -r rm --
ls -1t /etc/nginx/conf.d/{{ app_name }}-{{ deploy_env }}-upstream.conf.*~ 2>/dev/null | tail -n +4 | xargs -r rm --
args:
warn: false
+228
View File
@@ -0,0 +1,228 @@
# ============================================================================
# AUTO-GENERATED ENVIRONMENT FILE FOR AUTO-SCALING DEPLOYMENT
# Application: {{ app_name }}
# Environment: {{ deploy_env }}{{ env_tag | default('') }}
# Generated: {{ ansible_date_time.iso8601 }}
# Ansible User: {{ ansible_user_id }}
# ============================================================================
# Application Configuration
NODE_ENV={{ deploy_env }}
APP_NAME={{ app_name }}
APP_VERSION={{ docker_image_tag }}
DEPLOY_ENV={{ deploy_env }}
INSTANCE_ID={{ app_name }}-{{ deploy_env }}
# Port Configuration
PORT={{ final_container_port | default(container_port) | default(3000) }}
HOST=0.0.0.0
# Auto-Scaling Configuration
{% if auto_scaling_config is defined %}
REPLICAS={{ auto_scaling_config.replicas }}
MAX_REPLICAS={{ auto_scaling_config.max_replicas }}
DEPLOYMENT_STRATEGY={{ auto_scaling_config.deployment_strategy }}
BASE_PORT={{ auto_scaling_config.base_port }}
CONTAINER_PORT={{ auto_scaling_config.container_port }}
{% else %}
REPLICAS={{ final_replicas | default(2) }}
MAX_REPLICAS={{ final_max_replicas | default(5) }}
DEPLOYMENT_STRATEGY={{ final_deployment_strategy | default('rolling') }}
BASE_PORT={{ final_app_port | default(3000) }}
CONTAINER_PORT={{ final_container_port | default(3000) }}
{% endif %}
# Framework Configuration
{% if framework is defined %}
FRAMEWORK={{ framework }}
{% endif %}
{% if node_version is defined %}
NODE_VERSION={{ node_version }}
{% endif %}
{% if package_manager is defined %}
PACKAGE_MANAGER={{ package_manager }}
{% endif %}
# Docker Configuration
DOCKER_NETWORK={{ final_docker_network | default(app_name + '-' + deploy_env + '-network') }}
RESTART_POLICY={{ final_restart_policy | default('unless-stopped') }}
MEMORY_LIMIT={{ final_memory_limit | default('1g') }}
CPU_LIMIT={{ final_cpu_limit | default('1.0') }}
# Health Check Configuration
{% if health_check is defined %}
HEALTH_CHECK_PATH={{ health_check.path | default('/health') }}
HEALTH_CHECK_TIMEOUT={{ health_check.timeout | default(10) }}
HEALTH_CHECK_INTERVAL={{ health_check.interval | default(30) }}
HEALTH_CHECK_RETRIES={{ health_check.retries | default(3) }}
{% else %}
HEALTH_CHECK_PATH={{ health_path | default('/health') }}
HEALTH_CHECK_TIMEOUT=10
HEALTH_CHECK_INTERVAL=30
HEALTH_CHECK_RETRIES=3
{% endif %}
# Git Information
{% if git_tag is defined %}
GIT_TAG={{ git_tag }}
{% endif %}
{% if git_sha is defined %}
GIT_COMMIT={{ git_sha }}
{% endif %}
{% if git_branch is defined %}
GIT_BRANCH={{ git_branch }}
{% endif %}
# Build Information
BUILD_DATE={{ ansible_date_time.iso8601 }}
PIPELINE_ID={{ pipeline_id | default('unknown') }}
# Environment-Specific Variables
{% if environment_variables is defined and environment_variables[deploy_env] is defined %}
{% for key, value in environment_variables[deploy_env].items() %}
{{ key }}={{ value }}
{% endfor %}
{% endif %}
# Legacy env_vars support (backward compatibility)
{% if env_vars is defined %}
{% for key, value in env_vars.items() %}
{{ key }}={{ value }}
{% endfor %}
{% endif %}
# Project-Specific Variables
{% if project_env_vars is defined and project_env_vars[deploy_env] is defined %}
{% for key, value in project_env_vars[deploy_env].items() %}
{{ key }}={{ value }}
{% endfor %}
{% endif %}
# Database Configuration (if applicable)
{% if database_config is defined %}
{% for key, value in database_config.items() %}
{{ key | upper }}={{ value }}
{% endfor %}
{% endif %}
# Queue Configuration (if applicable)
{% if queue_config is defined %}
{% for key, value in queue_config.items() %}
{{ key | upper }}={{ value }}
{% endfor %}
{% endif %}
# Monitoring Configuration
{% if monitoring is defined %}
{% if monitoring.enable_metrics %}
ENABLE_METRICS=true
METRICS_PORT={{ monitoring.metrics_port | default(9090) }}
METRICS_PATH={{ monitoring.metrics_path | default('/metrics') }}
{% endif %}
{% if monitoring.enable_tracing %}
ENABLE_TRACING=true
JAEGER_ENDPOINT={{ monitoring.jaeger_endpoint | default('http://jaeger:14268/api/traces') }}
{% endif %}
{% if monitoring.log_aggregation %}
LOG_AGGREGATION=true
LOG_FORMAT={{ monitoring.log_format | default('json') }}
{% endif %}
{% endif %}
# Load Balancer Information
LB_UPSTREAM_NAME={{ app_name }}_{{ deploy_env }}_backend
LB_METHOD={{ nginx_config.load_balancing_method | default('least_conn') }}
# Service Discovery
SERVICE_NAME={{ app_name }}
SERVICE_VERSION={{ docker_image_tag }}
SERVICE_ENVIRONMENT={{ deploy_env }}
# Auto-scaling Metadata for Application
AUTOSCALE_ENABLED=true
AUTOSCALE_TARGET_REPLICAS={{ final_replicas | default(2) }}
AUTOSCALE_MAX_REPLICAS={{ final_max_replicas | default(5) }}
AUTOSCALE_STRATEGY={{ final_deployment_strategy | default('rolling') }}
# Deployment Metadata
DEPLOYMENT_TIMESTAMP={{ ansible_date_time.epoch }}
DEPLOYMENT_USER={{ ansible_user_id }}
DEPLOYMENT_HOST={{ ansible_host }}
# Security Configuration
{% if security_config is defined %}
{% for key, value in security_config.items() %}
{{ key | upper }}={{ value }}
{% endfor %}
{% endif %}
# Feature Flags
{% if feature_flags is defined %}
{% for key, value in feature_flags.items() %}
FEATURE_{{ key | upper }}={{ value }}
{% endfor %}
{% endif %}
# Next.js Specific (if framework is nextjs)
{% if framework == 'nextjs' %}
NEXT_TELEMETRY_DISABLED=1
{% if environment_variables is defined and environment_variables[deploy_env] is defined %}
{% for key, value in environment_variables[deploy_env].items() %}
{% if key.startswith('NEXT_PUBLIC_') %}
{{ key }}={{ value }}
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
# React Specific (if framework is reactjs)
{% if framework == 'reactjs' or framework == 'reactjs-vite' %}
{% if environment_variables is defined and environment_variables[deploy_env] is defined %}
{% for key, value in environment_variables[deploy_env].items() %}
{% if key.startswith('REACT_APP_') %}
{{ key }}={{ value }}
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
# NestJS Specific (if framework is nestjs)
{% if framework == 'nestjs' %}
NEST_DEBUG=false
{% if deploy_env == 'development' %}
NEST_DEBUG=true
{% endif %}
{% endif %}
# Express Specific (if framework is expressjs)
{% if framework == 'expressjs' %}
EXPRESS_ENV={{ deploy_env }}
TRUST_PROXY=true
{% endif %}
# Worker Specific (if it's a worker deployment)
{% if env_tag is defined and 'worker' in env_tag %}
WORKER_CONCURRENCY={{ worker_config.concurrency | default(5) }}
WORKER_TIMEOUT={{ worker_config.timeout | default(30000) }}
QUEUE_NAME={{ worker_config.queue_name | default('default') }}
QUEUE_RETRY_ATTEMPTS={{ worker_config.retry_attempts | default(3) }}
{% endif %}
# ============================================================================
# AUTO-SCALING DEPLOYMENT INFORMATION
# ============================================================================
# This file was auto-generated for auto-scaling deployment
# Management script: /home/{{ server_user }}/scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh
#
# Scale command: ./manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh scale <replicas>
# Status command: ./manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh status
# Health command: ./manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh health
# Logs command: ./manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh logs
#
# Port Pattern: {{ final_app_port | default(3000) }}{replica_number}
# Current Replicas: {{ final_replicas | default(2) }}
# Max Replicas: {{ final_max_replicas | default(5) }}
# Strategy: {{ final_deployment_strategy | default('rolling') }}
# ============================================================================
@@ -0,0 +1,93 @@
# Express.js Optimized Dockerfile
# Auto-generated for {{ app_name }}
# Node.js Version: {{ node_version }}
# Package Manager: {{ package_manager }}
# ============================================================================
# Build arguments
# ============================================================================
ARG NODE_VERSION={{ node_version }}
ARG PACKAGE_MANAGER={{ package_manager }}
ARG BUILD_COMMAND={{ build_command }}
ARG APP_PORT={{ framework_settings_port }}
ARG NODE_ENV=production
# ============================================================================
# Base stage
# ============================================================================
FROM gitlab.forlife.one:5050/share/node:${NODE_VERSION}-alpine AS base
# Install dependencies for native modules and health checks
RUN apk update && apk add imagemagick && apk add graphicsmagick && apk add tmux && apk add ffmpeg
RUN apk add --no-cache \
libc6-compat \
python3 \
make \
g++ \
curl \
dumb-init
# ============================================================================
# Production stage
# ============================================================================
FROM base AS production
COPY . ./app
WORKDIR /app
RUN npm install
# Build arguments
ARG GIT_COMMIT=unknown
ARG BUILD_DATE=unknown
# Set build environment
ENV NODE_ENV=production
# Create build info endpoint
RUN mkdir -p src/common && \
echo 'export const BUILD_INFO = {"version":"{{ docker_image_tag }}","commit":"'${GIT_COMMIT}'","buildDate":"'${BUILD_DATE}'","framework":"expressjs","nodeVersion":"{{ node_version }}"}' > src/common/build-info.ts
# Build app
RUN npm run build
# Create non-root user
RUN addgroup --system --gid 1001 expressjs && adduser --system --uid 1001 expressjs
# Ensure dist folder is owned by expressjs user
RUN chown -R expressjs:expressjs /app/dist
# Set environment variables
ENV NODE_ENV=production
ENV PORT=${APP_PORT}
ENV APP_PORT=${APP_PORT}
ENV NODE_OPTIONS="--max-old-space-size={{ MEMORY_LIMIT_MB }}"
RUN echo '#!/bin/sh\n\
status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${APP_PORT}/health)\n\
if [ "$status" -eq 200 ] || [ "$status" -eq 404 ]; then exit 0; fi\n\
\n\
status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${APP_PORT}/)\n\
if [ "$status" -eq 200 ] || [ "$status" -eq 404 ]; then exit 0; fi\n\
\n\
exit 1' > /health-check.sh && \
chmod +x /health-check.sh
# Create logs directory
RUN mkdir -p ./logs && chown -R expressjs:expressjs ./logs
RUN ([ -d "./src/public" ] && cp -r ./src/public ./dist && chown -R expressjs:expressjs ./dist/public) || true
# Switch to non-root user
USER expressjs
# Expose port
EXPOSE ${APP_PORT}
# Health check
#HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 CMD /health-check.sh
# Start the application with dumb-init for proper signal handling
#CMD ["dumb-init", "node", "dist/main"]
CMD {{ RUN_CMD }}
@@ -0,0 +1,106 @@
# Define a build-time variable for the PHP version, with a default of 8.1
ARG PHP_VERSION={{ php_version }}
# Stage 1: Build Stage (Optional but good practice)
# Use the PHP_VERSION argument in the FROM instruction
FROM gitlab.forlife.one:5050/share/php:${PHP_VERSION}-fpm-alpine AS base
# Set working directory inside the container
WORKDIR /var/www/html
# -----------------------------------------------
# 1. INSTALL SYSTEM DEPENDENCIES & PHP EXTENSIONS
# -----------------------------------------------
# Added redis extension installation via PECL.
RUN apk update && apk add --no-cache \
git \
curl \
libpng-dev \
libjpeg-turbo-dev \
libwebp-dev \
freetype-dev \
zip \
unzip \
libzip-dev \
oniguruma-dev \
autoconf \
g++ \
make \
nginx \
supervisor \
dcron \
postgresql-dev \
libxml2-dev \
&& pecl install redis \
&& docker-php-ext-enable redis \
# Configure and install other extensions
&& docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \
&& docker-php-ext-install -j$(nproc) gd pdo_mysql pdo_pgsql opcache bcmath exif pcntl sockets zip mbstring dom \
# Clean up
&& rm -rf /var/cache/apk/*
# Setup Laravel scheduler cron job to run every minute
# This cron job will be run by the 'www-data' user
RUN echo "* * * * * cd /var/www/html && php artisan schedule:run >> /dev/null 2>&1" > /etc/crontabs/www-data
# ============================================================================
# Production stage
# ============================================================================
FROM base AS production
WORKDIR /var/www/html
# -----------------------------------------------
# 2. APPLICATION SETUP & DEPENDENCIES
# -----------------------------------------------
# Install Composer v2, suitable for modern PHP versions
#COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
# Copy the Laravel application files
COPY . .
# Install Composer dependencies (Assuming composer.lock is present)
RUN composer install --no-dev --optimize-autoloader --no-interaction
# Optimize Laravel
RUN php artisan optimize && \
php artisan route:clear && \
php artisan route:cache && \
php artisan config:clear && \
php artisan config:cache && \
php artisan view:clear && \
php artisan view:cache
#RUN php artisan storage:link
# remove this line if you do not want to run migrations on each build
#RUN php artisan migrate --force
# Set appropriate permissions for Laravel (www-data is the default FPM user on Alpine)
RUN chown -R www-data:www-data /var/www/html \
&& chmod -R 775 storage bootstrap/cache
RUN ([ -d "public/cms/backend/uploads" ] && chmod -R 775 public/cms/backend/uploads) || true
# -----------------------------------------------
# 3. CONFIGURATION & ENTRYPOINT (THE FIX)
# -----------------------------------------------
RUN mkdir -p /run/nginx \
&& chown -R www-data:www-data /run/nginx \
&& chown -R www-data:www-data /var/lib/nginx
# Copy Nginx Configuration (Assuming it's in the 'docker' subdirectory)
# Nginx listens on port 80 and proxies PHP requests to FPM (port 9000).
COPY docker/nginx.conf /etc/nginx/nginx.conf
COPY docker/laravel.conf /etc/nginx/http.d/site.conf
COPY docker/php.ini /usr/local/etc/php/php.ini
# Copy Supervisor Configuration (Assuming it's in the 'docker' subdirectory)
# Supervisor manages both Nginx and PHP-FPM processes.
COPY docker/supervisord.conf /etc/supervisord.conf
EXPOSE 3000
# Start Supervisor, which runs Nginx, PHP-FPM, and Cron
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]
@@ -0,0 +1,94 @@
# Express.js Optimized Dockerfile
# Auto-generated for {{ app_name }}
# Node.js Version: {{ node_version }}
# Package Manager: {{ package_manager }}
# ============================================================================
# Build arguments
# ============================================================================
ARG NODE_VERSION={{ node_version }}
ARG PACKAGE_MANAGER={{ package_manager }}
ARG BUILD_COMMAND={{ build_command }}
ARG APP_PORT={{ framework_settings_port }}
ARG NODE_ENV=production
# ============================================================================
# Base stage
# ============================================================================
FROM gitlab.forlife.one:5050/share/node:${NODE_VERSION}-alpine AS base
# Install dependencies for native modules and health checks
RUN apk update && apk add imagemagick && apk add graphicsmagick && apk add tmux && apk add ffmpeg
RUN apk add --no-cache \
libc6-compat \
python3 \
make \
g++ \
curl \
dumb-init
# ============================================================================
# Production stage
# ============================================================================
FROM base AS production
COPY . ./app
WORKDIR /app
RUN yarn install
# Build arguments
ARG GIT_COMMIT=unknown
ARG BUILD_DATE=unknown
# Set build environment
ENV NODE_ENV=production
# Create build info endpoint
RUN mkdir -p src/common && \
echo 'export const BUILD_INFO = {"version":"{{ docker_image_tag }}","commit":"'${GIT_COMMIT}'","buildDate":"'${BUILD_DATE}'","framework":"nestjs","nodeVersion":"{{ node_version }}"}' > src/common/build-info.ts
# Build app
RUN npm run build
# Create non-root user
RUN addgroup --system --gid 1001 nestjs && adduser --system --uid 1001 nestjs
# Ensure dist folder is owned by nestjs user
RUN chown -R nestjs:nestjs /app/dist
# Set environment variables
ENV NODE_ENV=production
ENV PORT=${APP_PORT}
ENV APP_PORT=${APP_PORT}
ENV NODE_OPTIONS="--max-old-space-size={{ MEMORY_LIMIT_MB }}"
# Create health check script
RUN echo '#!/bin/sh\n\
status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${APP_PORT}/health)\n\
if [ "$status" -eq 200 ] || [ "$status" -eq 404 ]; then exit 0; fi\n\
\n\
status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${APP_PORT}/)\n\
if [ "$status" -eq 200 ] || [ "$status" -eq 404 ]; then exit 0; fi\n\
\n\
exit 1' > /health-check.sh && \
chmod +x /health-check.sh
# Create logs directory
RUN mkdir -p ./logs && chown -R nestjs:nestjs ./logs
RUN ([ -d "./src/public" ] && cp -r ./src/public ./dist && chown -R nestjs:nestjs ./dist/public) || true
# Switch to non-root user
USER nestjs
# Expose port
EXPOSE ${APP_PORT}
# Health check
#HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 CMD /health-check.sh
# Start the application with dumb-init for proper signal handling
#CMD ["dumb-init", "node", "dist/main"]
CMD {{ RUN_CMD }}
@@ -0,0 +1,103 @@
# Next.js Optimized Dockerfile with Dynamic Build Args
# Auto-generated for {{ app_name }}
# Node.js Version: {{ node_version }}
# Package Manager: {{ package_manager }}
# ============================================================================
# Build arguments - Base Configuration
# ============================================================================
ARG NODE_VERSION={{ node_version }}
ARG PACKAGE_MANAGER={{ package_manager }}
ARG BUILD_COMMAND={{ build_command }}
ARG START_COMMAND={{ start_command }}
ARG APP_PORT={{ framework_settings_port }}
ARG NODE_ENV=production
ARG APP_VERSION={{ docker_image_tag }}
ARG FRAMEWORK=nextjs
# ============================================================================
# Base stage - Alpine for smaller size
# ============================================================================
FROM gitlab.forlife.one:5050/share/node:${NODE_VERSION}-alpine AS base
# Install dependencies
RUN apk add --no-cache \
libc6-compat \
python3 \
make \
g++ curl
# Install dependencies only when needed
FROM base AS deps
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
else {{ package_manager }} install; \
fi
#RUN {{ package_manager }} install
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED=1
RUN yarn build
# If using npm comment out above and use below instead
# RUN npm run build
# Production image, copy all the files and run next
FROM base AS production
WORKDIR /app
ENV NODE_ENV=production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_OPTIONS="--max-old-space-size={{ MEMORY_LIMIT_MB }}"
# Create health check script
RUN echo '#!/bin/sh\n\
status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${APP_PORT}/health)\n\
if [ "$status" -eq 200 ] || [ "$status" -eq 404 ]; then exit 0; fi\n\
\n\
status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${APP_PORT}/)\n\
if [ "$status" -eq 200 ] || [ "$status" -eq 404 ]; then exit 0; fi\n\
\n\
exit 1' > /health-check.sh && \
chmod +x /health-check.sh
# You only need to copy next.config.js if you are NOT using the default configuration
# COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
ENV HOSTNAME=0.0.0.0
ENV PORT=3000
ENV APP_PORT=3000
# Health check
#HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 CMD /health-check.sh
#CMD ["node", "server.js"]
CMD {{ RUN_CMD }}
@@ -0,0 +1,189 @@
# React.js Optimized Dockerfile (No Nginx)
# Auto-generated for {{ app_name }}
# Node.js Version: {{ node_version }}
# Package Manager: {{ package_manager }}
# ============================================================================
# Build arguments
# ============================================================================
ARG NODE_VERSION={{ node_version }}
ARG PACKAGE_MANAGER={{ package_manager }}
ARG BUILD_COMMAND={{ build_command }}
ARG APP_PORT={{ framework_settings_port }}
# ============================================================================
# Base stage
# ============================================================================
FROM gitlab.forlife.one:5050/share/node:${NODE_VERSION}-alpine AS base
# Install dependencies
RUN apk add --no-cache \
libc6-compat \
python3 \
make \
g++ curl
WORKDIR /app
# ============================================================================
# Dependencies stage
# ============================================================================
FROM base AS deps
# Copy package files
COPY package*.json yarn.lock* pnpm-lock.yaml* ./
# Install dependencies
#RUN yarn install
RUN {{ package_manager }} install
# ============================================================================
# Builder stage
# ============================================================================
FROM base AS builder
# Copy dependencies
COPY --from=deps /app/node_modules ./node_modules
# Copy source code
COPY . .
# Build arguments for runtime configuration
ARG GIT_COMMIT=unknown
ARG BUILD_DATE=unknown
ARG REACT_APP_VERSION={{ docker_image_tag }}
# Set build environment
ENV NODE_ENV=production
ENV GENERATE_SOURCEMAP=false
ENV REACT_APP_VERSION=${REACT_APP_VERSION}
ENV REACT_APP_BUILD_DATE=${BUILD_DATE}
ENV REACT_APP_GIT_COMMIT=${GIT_COMMIT}
# Create build info file
RUN echo '{"version":"{{ docker_image_tag }}","commit":"'${GIT_COMMIT}'","buildDate":"'${BUILD_DATE}'","framework":"reactjs","nodeVersion":"{{ node_version }}"}' > public/build-info.json
# Require BuildKit enabled
# Mount secret nếu tồn tại, copy vào thư mục app
RUN --mount=type=secret,id=env,target=/run/secrets/env \
cp /run/secrets/env .env || echo ".env not found"
# ============================================================================
# Build the application
# ============================================================================
RUN \
if [ -f yarn.lock ]; then \
yarn build; \
elif [ -f package-lock.json ]; then \
npm run build; \
elif [ -f pnpm-lock.yaml ]; then \
pnpm build; \
else \
npm run build; \
fi
# ============================================================================
# Production stage with serve
# ============================================================================
FROM node:${NODE_VERSION}-alpine AS production
# Install curl for health checks
RUN apk add --no-cache curl
WORKDIR /app
# Install serve globally for serving static files
RUN npm install -g serve
# Copy built React app
COPY --from=builder /app/build ./build
# Create package.json for serving
RUN echo '{ \
"name": "{{ app_name }}-server", \
"version": "{{ docker_image_tag }}", \
"scripts": { \
"start": "serve -s build -l 3000" \
}, \
"dependencies": { \
"serve": "latest" \
} \
}' > package.json
# Create serve.json configuration
RUN echo '{ \
"public": "build", \
"rewrites": [ \
{ "source": "**", "destination": "/index.html" } \
], \
"headers": [ \
{ \
"source": "**/*.@(jpg|jpeg|gif|png|svg|ico|css|js)", \
"headers": [ \
{ \
"key": "Cache-Control", \
"value": "public, max-age=31536000, immutable" \
} \
] \
}, \
{ \
"source": "**", \
"headers": [ \
{ \
"key": "X-Content-Type-Options", \
"value": "nosniff" \
}, \
{ \
"key": "X-Frame-Options", \
"value": "DENY" \
}, \
{ \
"key": "X-XSS-Protection", \
"value": "1; mode=block" \
} \
] \
} \
] \
}' > serve.json
# Create health check endpoint
RUN mkdir -p build && echo 'healthy' > build/health.txt
# Create health check script
RUN echo '#!/bin/sh\n\
status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${APP_PORT}/health)\n\
if [ "$status" -eq 200 ] || [ "$status" -eq 404 ]; then exit 0; fi\n\
\n\
status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${APP_PORT}/)\n\
if [ "$status" -eq 200 ] || [ "$status" -eq 404 ]; then exit 0; fi\n\
\n\
exit 1' > /health-check.sh && \
chmod +x /health-check.sh
# Create non-root user
RUN addgroup --system --gid 1001 reactapp && \
adduser --system --uid 1001 reactapp
# Set proper permissions
RUN chown -R reactapp:reactapp /app
# Switch to non-root user
USER reactapp
# Expose port
EXPOSE 3000
# Set environment variables
ENV NODE_ENV=production
ENV PORT=3000
ENV APP_PORT=3000
ENV NODE_OPTIONS="--max-old-space-size={{ MEMORY_LIMIT_MB }}"
# Health check
#HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 CMD /health-check.sh
# Start the application
#CMD ["npm", "start"]
#CMD [ "serve", "-s", "build" ]
CMD {{ RUN_CMD }}
+21
View File
@@ -0,0 +1,21 @@
#!/bin/sh
set -e # Thoát ngay lập tức nếu có lệnh nào thất bại
echo "Checking /health endpoint..."
health_status=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${PORT}/health")
if [ "$health_status" -eq 200 ] || [ "$health_status" -eq 404 ]; then
echo "/health check passed with status ${health_status}"
exit 0
fi
echo "/health check failed with status ${health_status}. Checking / endpoint..."
root_status=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${PORT}/")
if [ "$root_status" -eq 200 ] || [ "$root_status" -eq 404 ]; then
echo "/ check passed with status ${root_status}"
exit 0
fi
echo "/ check failed with status ${root_status}. Health check failed overall."
exit 1
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,182 @@
#!/usr/bin/env bash
# method 1: crontab with parallel block
#* * * * * flock -n /var/lock/monitor-container-script.lock /www/server/scripts/monitor-container-script.sh >>/var/log/monitor-container-script.log 2>&1
set -Eeuo pipefail
# method 2: crontab with parallel block
# only use method 1 or method 2
# Khóa tiến trình (FD 9)
#~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~
exec 9>/var/lock/monitor-container-script.lock
if ! flock -n 9; then
echo "[$(date '+%F %T')] ⚠️ Another run is in progress. Exit."
exit 0
fi
trap 'flock -u 9' EXIT
#~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~
# === Cấu hình chung ===
CONTAINER_PREFIX="webapplication-production-"
CONTAINER_PORT=3000 # cổng bên trong container (không cần dùng ở đây)
HOST="localhost"
TIMEOUT=3
SLEEP_TIME=10
COOLDOWN=180 # giây
CPU_LIMIT=80
MEM_LIMIT=90
MANAGE_SCRIPT="/home/forlifeci/scripts/manage-webapplication-production.sh restart_single"
CONTAINER_LOCK_FILE="/var/lib/container_maps.lock"
LOCK_DIR="/tmp/container_restart_locks" && mkdir -p "$LOCK_DIR/logs"
remove_timestamp() { [[ $1 =~ ^(.*-[0-9]+)-([0-9]+)$ ]] && echo "${BASH_REMATCH[1]}" || echo "$1"; }
check_container_health() {
local container=$1
local cpu mem
echo "📡 $(date '+%Y-%m-%d %H:%M:%S') - Kiểm tra container... $container"
#cpu=$(docker stats --no-stream --format "{{.CPUPerc}}" "$container" | tr -d '%')
#mem=$(docker stats --no-stream --format "{{.MemPerc}}" "$container" | tr -d '%')
#echo "Kiểm tra CPU / RAM quá giới hạn -> $container"
#if (( $(echo "$cpu > $CPU_LIMIT" | bc -l) )) || (( $(echo "$mem > $MEM_LIMIT" | bc -l) )); then
# echo "⚠️ $container quá tải (CPU: $cpu%, RAM: $mem%)"
# return 1
#fi
#echo "Kiểm tra port 3000 bên trong container -> $container"
if ! docker exec "$container" sh -c "nc -z -w 2 127.0.0.1 3000" >/dev/null 2>&1; then
echo "❌ $container không phản hồi trên port 3000"
return 1
fi
return 0
}
# === Telegram Config ===
TELEGRAM_BOT_TOKEN="7837210802:AAHFlwXOdm3PExgIbADJ_TstNoXwJ_Aq7g4"
TELEGRAM_CHAT_ID="-1002468192435"
THREAD_ID="10248"
# === Hàm gửi cảnh báo Telegram ===
send_telegram_alert() {
local container=$1
local port=$2
local fileName=${3:-"log"}
local now=$(date '+%Y-%m-%d %H:%M:%S')
local msg="🚨 *CẢNH BÁO* 🚨
Container: \`$container\`
Port: \`$port\`
Thời gian: *$now*
Hành động: \`docker restart\`
➡️ Container không phản hồi, đã được khởi động lại.
http://42.118.103.30/logs/$container.restart.$fileName.html
"
curl -s -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage" \
-d chat_id="$TELEGRAM_CHAT_ID" \
-d parse_mode="Markdown" \
-d message_thread_id="$THREAD_ID" \
-d text="$msg" > /dev/null
}
# === Hàm kiểm tra port ===
#check_port() {
# local port=$1
# nc -z -w $TIMEOUT $HOST $port
#}
check_port() {
local port=$1
curl -s --max-time "$TIMEOUT" "http://$HOST:$port" -o /dev/null
}
should_skip_check() {
local container=$1
local lock_file="$LOCK_DIR/$container.lock"
if [[ -f "$lock_file" ]]; then
local last_restart
last_restart=$(cat "$lock_file")
local now
now=$(date +%s)
if (( now - last_restart < COOLDOWN )); then
return 0 # bỏ qua
else
rm -f "$lock_file" # hết hạn cooldown
fi
fi
return 1
}
mark_restart() {
local container=$1
date +%s > "$LOCK_DIR/$container.lock"
}
get_docker_binding_port() {
local container=$1
echo $(docker port "$container" 3000/tcp | awk -F: '{print $2}' | xargs)
}
containers=$(docker ps --format '{{.Names}}' | grep "^$CONTAINER_PREFIX")
# === Vòng lặp kiểm tra ===
#while true; do
for container in $containers; do
if [[ -f $CONTAINER_LOCK_FILE ]]; then
break
fi
container_locked=$(remove_timestamp "$container")
# 🔹 Bỏ qua nếu container đang trong cooldown
if should_skip_check "$container_locked"; then
echo "⏩ Bỏ qua $container_locked (đang cooldown sau khi restart)"
continue
fi
host_port=$(get_docker_binding_port "$container")
if [ -z "$host_port" ]; then
echo "⚠ $container không map port 3000/tcp, bỏ qua."
continue
fi
echo "";
echo "👉 Kiểm tra $container tại $HOST:$host_port..."
if ! check_port $host_port; then
echo "⚠️ $container KHÔNG phản hồi. Chờ 5s để xác nhận..."
sleep 5
if ! check_port $host_port; then
echo "❌ $container vẫn không phản hồi! Restart & gửi Telegram..."
logName=$(date +%s)
mark_restart "$container_locked" # đánh dấu cooldown
#docker restart "$container"
restart=`bash $MANAGE_SCRIPT $container`
logPath="$LOCK_DIR/logs/$container.restart.$logName.html"
if [ ! -f "$logPath" ]; then
echo "<!DOCTYPE html><html><meta charset=utf-8><style>pre{text-wrap: auto;}</style>" > "$logPath"
fi
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~<br>" >> "$logPath"
printf "%s" "<pre>$restart</pre>" >> "$logPath"
send_telegram_alert "$container" "$host_port" "$logName"
else
echo "✅ $container đã phản hồi lại sau khi chờ."
fi
else
echo "✅ $container phản hồi OK."
fi
done
# sleep $SLEEP_TIME
#done
#/home/forlifeci/scripts/manage-webapplication-production.sh restart_single $container
@@ -0,0 +1,140 @@
# ============================================================================
# AUTO-GENERATED NGINX UPSTREAM CONFIGURATION
# Application: {{ app_name }}
# Environment: {{ deploy_env }}{{ env_tag | default('') }}
# Generated: {{ ansible_date_time.iso8601 }}
# Replicas: {{ replica_count }}
# ============================================================================
{% if framework is defined and framework == "nextjs" %}
map $arg_url ${{app_name | regex_replace('[^A-Za-z0-9_]', '_')}}_clean_url {
~^(?<base>.+)%3Fv%3D\d+$ $base; # chỉ có ?v=xxx
~^(?<base>.+)%3Fv%3D\d+%26(?<rest>.+)$ $base?$rest; # có ?v=xxx&... → giữ lại phần sau
default $arg_url; # mặc định giữ nguyên
}
{% endif %}
upstream {{ upstream_name | regex_replace('[^A-Za-z0-9_]', '_') }} {
# Load balancing method
{% if load_balancing_method is defined %}
{{ load_balancing_method }};
{% else %}
least_conn;
{% endif %}
# Health check configuration
{% if health_check_interval is defined %}
health_check interval={{ health_check_interval }} fails={{ health_check_fails | default(1) }} passes={{ health_check_passes | default(2) }};
{% endif %}
# Upstream servers (auto-generated from active ports)
{% for port in active_ports %}
server localhost:{{ port }} weight={{ server_weight | default(1) }} max_fails={{ max_fails | default(1) }} fail_timeout={{ fail_timeout | default(10) }}s;
{% endfor %}
# Backup server (optional)
{% if backup_server is defined %}
server {{ backup_server }} backup;
{% endif %}
# Keep-alive connections
keepalive {{ keepalive_connections | default(64) }};
}
{% if framework is defined and framework == "nextjs" %}
proxy_cache_path /var/cache/nginx/{{app_name | regex_replace('[^A-Za-z0-9_]', '_')}}_cache levels=1:2 keys_zone={{app_name | regex_replace('[^A-Za-z0-9_]', '_')}}_cache:512m inactive=30d max_size=100g use_temp_path=off;
#levels=1:2: Tạo cây thư mục theo hash key để phân bổ file.
#keys_zone=tkl_web_img_cache:512m: Tăng RAM dùng cho metadata (1MB chứa được ~8000 keys) → 2G = ~16 triệu keys.
#inactive=30d: Nếu file không được truy cập trong 30 ngày → bị xóa.
#max_size=100g: Giới hạn tối đa 100GB cache disk.
#use_temp_path=off: Ghi thẳng vào cache thay vì ghi tạm → hiệu quả hơn.
{% endif %}
server {
listen {{ base_port }};
server_name localhost;
{% if framework is defined and framework == "nextjs" %}
location ~* /_next/image {
access_log off;
proxy_pass http://{{ upstream_name | regex_replace('[^A-Za-z0-9_]', '_') }}; # upstream load balancing
proxy_cache {{app_name | regex_replace('[^A-Za-z0-9_]', '_')}}_cache;
proxy_cache_key "url=${{app_name | regex_replace('[^A-Za-z0-9_]', '_')}}_clean_url&w=$arg_w&q=$arg_q";
proxy_cache_valid 200 180d;
proxy_cache_use_stale error timeout updating http_502 http_503 http_504;
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 3;
# Chống cache stampede (bottleneck)
proxy_cache_lock on;
proxy_cache_lock_timeout 10s;
proxy_cache_lock_age 5s;
# Timeout & buffering
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
send_timeout 60s;
# Ignore upstream cache headers (Next.js gửi max-age + vary)
proxy_ignore_headers Cache-Control Vary;
proxy_hide_header Set-Cookie;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
# Chuẩn hóa Accept để tránh tạo nhiều cache key khác nhau
proxy_set_header Accept "image/webp,image/*,*/*;q=0.8";
add_header X-Cache-Status $upstream_cache_status;
#add_header X-Cache-Key "url=${{app_name | regex_replace('[^A-Za-z0-9_]', '_')}}_clean_url&w=$arg_w&q=$arg_q";
# Keepalive settings (optional)
keepalive_timeout 64;
}
{% endif %}
location / {
access_log off;
proxy_pass http://{{ upstream_name | regex_replace('[^A-Za-z0-9_]', '_') }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
proxy_cache off;
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 3;
# Timeout & buffering
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
send_timeout 60s;
proxy_buffering on;
client_max_body_size 256M;
# Keepalive settings (optional)
keepalive_timeout 64;
}
}
# ============================================================================
# CONFIGURATION METADATA (for debugging)
# ============================================================================
# Configuration Details:
# - Base Port: {{ base_port }}
# - Load Balancing: {{ load_balancing_method | default('least_conn') }}
# - Max Fails: {{ max_fails | default(1) }}
# - Fail Timeout: {{ fail_timeout | default(10) }}s
# - Health Check: {{ 'enabled' if enable_health_check_endpoint | default(true) else 'disabled' }}
# ============================================================================
+63
View File
@@ -0,0 +1,63 @@
server {
listen 3000;
server_name localhost;
root /var/www/html/public; # Đây là thư mục public của Laravel
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
index index.html index.htm index.php;
charset utf-8;
#access_log /dev/stdout;
#error_log /dev/stdout;
location ~\.(yml|env|example|log|prod|dev|local|production)$ {
deny all;
return 404;
}
location ~* \.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|pdf|txt|tar|wav|bmp|rtf|js|flv|swf|mp4|vtt)$ {
access_log off;
log_not_found off;
expires 30d;
add_header Access-Control-Allow-Origin *;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ "^(.+\.php)($|/)" {
index index.php index.html index.htm admin.php;
fastcgi_index index.php;
fastcgi_split_path_info ^(.+\.php)(.*)$;
fastcgi_pass 127.0.0.1:9000;
include fastcgi_params;
fastcgi_param HTTPS off;
send_timeout 120;
fastcgi_read_timeout 120;
client_max_body_size 20m;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
+108
View File
@@ -0,0 +1,108 @@
#user nginx;
user www-data;
# Set number of worker processes automatically based on number of CPU cores.
worker_processes auto;
# Enables the use of JIT for regular expressions to speed-up their processing.
pcre_jit on;
# Configures default error logger.
error_log /var/log/nginx/error.log warn;
# Includes files with directives to load dynamic modules.
include /etc/nginx/modules/*.conf;
# Include files with config snippets into the root context.
include /etc/nginx/conf.d/*.conf;
events {
# The maximum number of simultaneous connections that can be opened by
# a worker process.
worker_connections 1024;
}
http {
# Includes mapping of file name extensions to MIME types of responses
# and defines the default type.
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Name servers used to resolve names of upstream servers into addresses.
# It's also needed when using tcpsocket and udpsocket in Lua modules.
#resolver 1.1.1.1 1.0.0.1 [2606:4700:4700::1111] [2606:4700:4700::1001];
# Don't tell nginx version to the clients. Default is 'on'.
server_tokens off;
# Specifies the maximum accepted body size of a client request, as
# indicated by the request header Content-Length. If the stated content
# length is greater than this size, then the client receives the HTTP
# error code 413. Set to 0 to disable. Default is '1m'.
client_max_body_size 100m;
# Sendfile copies data between one FD and other from within the kernel,
# which is more efficient than read() + write(). Default is off.
sendfile on;
# Causes nginx to attempt to send its HTTP response head in one packet,
# instead of using partial frames. Default is 'off'.
tcp_nopush on;
# Enables the specified protocols. Default is TLSv1 TLSv1.1 TLSv1.2.
# TIP: If you're not obligated to support ancient clients, remove TLSv1.1.
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
# Path of the file with Diffie-Hellman parameters for EDH ciphers.
# TIP: Generate with: `openssl dhparam -out /etc/ssl/nginx/dh2048.pem 2048`
#ssl_dhparam /etc/ssl/nginx/dh2048.pem;
# Specifies that our cipher suits should be preferred over client ciphers.
# Default is 'off'.
ssl_prefer_server_ciphers on;
# Enables a shared SSL cache with size that can hold around 8000 sessions.
# Default is 'none'.
ssl_session_cache shared:SSL:2m;
# Specifies a time during which a client may reuse the session parameters.
# Default is '5m'.
ssl_session_timeout 1h;
# Disable TLS session tickets (they are insecure). Default is 'on'.
ssl_session_tickets off;
# Enable gzipping of responses.
gzip on;
# Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'.
#gzip_vary on;
# Helper variable for proxying websockets.
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# Specifies the main log format.
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# Sets the path, format, and configuration for a buffered log write.
access_log /var/log/nginx/access.log main;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
set_real_ip_from 0.0.0.0/0;
#access_log /dev/stdout;
#error_log /dev/stdout;
# Includes virtual hosts configs.
include /etc/nginx/http.d/*.conf;
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,39 @@
[supervisord]
nodaemon=true
# (Tùy chọn) Ngăn Supervisor tự tạo log file của chính nó
logfile=/dev/null
logfile_maxbytes=0
[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true
priority=10
# Chuyển hướng stdout của Nginx ra stdout của Supervisor
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
# Chuyển hướng stderr của Nginx ra stderr của Supervisor
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:php-fpm]
command=/usr/local/sbin/php-fpm --nodaemonize --fpm-config /usr/local/etc/php-fpm.conf
autostart=true
autorestart=true
priority=5
# Chuyển hướng stdout của PHP-FPM ra stdout của Supervisor
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
# Chuyển hướng stderr của PHP-FPM ra stderr của Supervisor
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:queue-work]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/artisan queue:work
user=www-data
autostart=true
autorestart=true
numprocs=1
redirect_stderr=true
stdout_logfile=/dev/stdout