FROM postgres:14.1-alpine
ENV POSTGRES_DB=db \
POSTGRES_USER=usr \
POSTGRES_PASSWORD=pwd
COPY ./initdb /docker-entrypoint-initdb.d/
- FROM définit l'image de base du conteneur, dans ce cas, PostgreSQL.
- COPY transfère un fichier depuis la machine hôte vers un répertoire spécifique du conteneur Docker.
- ENV sert à définir des variables d'environnement au sein du conteneur Docker.
# Create network, communication entre 2 conteneurs, il faut créer un réseau commun
docker network create app-network
# To restart admirer
docker run -d --name adminer --network app-network -p 8090:8080 adminer
# Build docker image
docker build -t mypostgres .
# Run the PostgreSQL Container with Volume
docker run -p 5432:5432 --net=app-network --name mypostgres -v data:/var/lib/postgresql/data mypostgres
-p exposer les ports du conteneur sur la machine hôte.
--name sert à attribuer un nom au conteneur.
-v gère les volumes pour la persistance des données, sinon si container détruit, données conservées
#Run Adminer
docker run -p "8090:8080" --net=app-network --name=adminer -d adminer
1-1 Why should we run the container with a flag -e to give the environment variables?
Utiliser l'option -e
pour passer des variables d'environnement lors de l'exécution est plus sûr que de les écrire directement dans le Dockerfile. Cela évite de stocker des informations sensibles, comme des mots de passe, en clair dans le fichier, ce qui pourrait poser un risque si le Dockerfile est partagé ou enregistré dans un gestionnaire de versions.
1-2 Why do we need a volume to be attached to our postgres container?
Attacher un volume au conteneur PostgreSQL garantit que les données de la base de données sont stockées de manière persistante sur la machine hôte. Ainsi, même si le conteneur est détruit ou recréé, les données restent intactes et peuvent être réutilisées par la nouvelle instance du conteneur.
1-3 Document your database container essentials: commands and Dockerfile.
Done
# Build
FROM maven:3.9.9-amazoncorretto-21 AS myapp-build
ENV MYAPP_HOME=/opt/myapp
WORKDIR $MYAPP_HOME
COPY /simpleapi/pom.xml .
COPY /simpleapi/src ./src
RUN mvn package -DskipTests
# Run
FROM amazoncorretto:21
ENV MYAPP_HOME=/opt/myapp
WORKDIR $MYAPP_HOME
COPY --from=myapp-build $MYAPP_HOME/target/*.jar $MYAPP_HOME/myapp.jar
ENTRYPOINT ["java", "-jar", "myapp.jar"]
Un build multistage permet d'utiliser plusieurs instructions FROM dans un seul Dockerfile. Chaque instruction FROM démarre une nouvelle étape, et nous pouvons sélectionner les artefacts à copier d'une étape à l'autre. Le build contient le JDK nécessaire pour compiler l'application avec Maven Le run contient uniquement le JRE, suffisant pour éxecuter l'application déjà compilée, rendant l'image plus légère. La première étape génère les artefacts nécessaires, tandis que la seconde étape ne copie que les artefacts essentiels dans l'image finale, l'image finale sera plus petite et performante.
FROM maven:3.9.9-amazoncorretto-21 AS myapp-build
Utilise Maven et le JDK Amazon Corretto pour compiler l'application.
WORKDIR /opt/myapp
COPY /simpleapi/pom.xml .
COPY /simpleapi/src ./src
RUN mvn package -DskipTests
Définit le répertoire de travail, copie le code source et compile l'application en un fichier JAR.
FROM amazoncorretto:21
Utilise l'image JRE Amazon Corretto pour l'exécution de l'application.
COPY --from=myapp-build /opt/myapp/target/*.jar /opt/myapp/myapp.jar
ENTRYPOINT ["java", "-jar", "myapp.jar"]
Copie le JAR compilé et exécute l'application avec java -jar
.
Cette approche sépare la construction (avec JDK) et l'exécution (avec JRE) pour une image finale plus légère et optimisée.
1-5 Why do we need a reverse proxy?
Un reverse proxy est utilisé pour rediriger les requêtes des clients vers un ou plusieurs serveurs backend, en agissant comme un intermédiaire. Avantages : sécurité en masquant le serveur backend, gestion du SSL/TLS etc .. Dans notre cas, il redirige les requêtes HTTP vers l'application backend de manière simple.
# database
docker build -t mypostgres .
docker run -p 5432:5432 --net=app-network --name mypostgres -v data:/var/lib/postgresql/data mypostgres
# API
docker build -t simple-api-student .
docker run -p 8080:8080 --net=app-network --name simple-api-student simple-api-student
# Server, 8040 car port 80:80 ne marchait vraiment pas
docker build -t simple-http-server .
docker run -p 8040:80 --net=app-network --name simple-http-server -d simple-http-server
# To check
docker stats simple-http-server
docker inspect simple-http-server
docker logs simple-http-server
Dockerfile
:
FROM httpd:2.4
COPY httpd.conf /usr/local/apache2/conf/httpd.conf
COPY ./index.html /usr/local/apache2/htdocs/
httpd.conf
que on a récupéré avec :
docker cp simple-http-server:/usr/local/apache2/conf/httpd.conf ./httpd.conf
<VirtualHost *:80>
ProxyPreserveHost On
ProxyPass / http://simple-api-student:8080/
ProxyPassReverse / http://simple-api-student:8080/
</VirtualHost>
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
Avantages de l'utilisation de docker cp
pour récupérer httpd.conf
:
- Personnalisation : Ajuster la configuration selon les besoins.
- Cohérence : Configuration par défaut qui fonctionne, puis y apporter des modifications.
- Contrôle de version : Garder le fichier de configuration personnalisé, avec le Dockerfile et autres fichiers.
1-6 Why is docker-compose so important?
Docker-compose est important car il permet de gérer plusieurs conteneurs Docker avec une seule commande. Il simplifie le déploiement et l'organisation d'applications utilisant plusieurs conteneurs, en facilitant le démarrage, l'arrêt et la gestion des services d'une application. Cela permet de gagner du temps et d'assurer que la configuration est fiable et peut être reproduite facilement.
1-8 Document docker-compose most important commands.
-
Démarrer les conteneurs :
docker-compose up
-
Démarrer les conteneurs en mode détaché (en arrière-plan) :
docker-compose up -d
-
Arrêter et supprimer les conteneurs :
docker-compose down
-
Mettre en pause les conteneurs :
docker-compose pause
-
Reprendre les conteneurs mis en pause :
docker-compose unpause
-
Voir les logs des conteneurs :
docker-compose logs
-
Construire les images :
docker-compose build
1-8 Document your docker-compose file.
Voici un exemple de structure de fichier docker-compose.yml
:
version: '3.7'
services:
backend:
build:
context: ./backend_api/app2
dockerfile: Dockerfile
container_name: simple-api-student
networks:
- app-network
depends_on:
- database
database:
build:
context: ./postgres
dockerfile: Dockerfile
container_name: mypostgres
networks:
- app-network
volumes:
- ./data:/var/lib/postgresql/data
httpd:
build:
context: ./http_server
dockerfile: Dockerfile
container_name: simple-http-server
networks:
- app-network
ports:
- 8040:80
networks:
app-network:
-
backend :
- Construit l'image à partir de
./backend_api/app2
avec le Dockerfile. - Dépend de
database
et utilise le réseauapp-network
.
- Construit l'image à partir de
-
database :
- Construit l'image à partir de
./postgres
. - Utilise un volume pour persister les données dans
./data
et se connecte au réseauapp-network
.
- Construit l'image à partir de
-
httpd :
- Construit l'image à partir de
./http_server
. - Expose le port
8040
local vers le port80
du conteneur et utilise le réseauapp-network
.
- Construit l'image à partir de
networks:
app-network:
Crée un réseau app-network
pour que tous les services puissent communiquer entre eux.
En résumé, ce fichier configure un backend, une base de données PostgreSQL et un serveur HTTP, tous connectés via un réseau privé.
1-9 Document your publication commands and published images in dockerhub.
# Se connecter à Docker Hub :
docker login
#Taguer votre image :
docker tag mypostgres wanoni/my-database:1.0
# Pousser l'image vers Docker Hub :
docker push wanoni/my-database:1.0
# de même avec api et server
1-10 Why do we put our images into an online repo?
Mettre nos images dans un dépôt en ligne comme Docker Hub permet de les partager facilement avec d'autres, de les sauvegarder, d'y accéder à distance et de gérer les versions. C'est essentiel pour collaborer et déployer de manière efficace.
2-1 What are testcontainers?
They simply are java libraries that allow you to run a bunch of docker containers while testing. Here we use the postgresql container to attach to our application while testing.
2-2 Document your Github Actions configurations.
Github Action :
- pour tester le backend à chaque commit sur la branch main
- le publier
## Bonus: split pipelines (Optional)
Build : On teste le backend sur le merge sur main
```yml
name: Test Backend
on:
push:
branches:
- main
- develop
pull_request:
jobs:
test-backend:
runs-on: ubuntu-22.04
steps:
# Checkout your GitHub code using actions/checkout@v4
- uses: actions/checkout@v4
# Set up JDK 21 using actions/setup-java@v3
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
distribution: 'corretto'
java-version: '21'
# Build and test the project using Maven
- name: Build and test with Maven
run: mvn -B verify sonar:sonar -Dsonar.projectKey=devops-project12345 -Dsonar.organization=devops-sonar123 -Dsonar.host.url=https://sonarcloud.io -Dsonar.token=${{ secrets.SONAR_TOKEN }}
working-directory: simple-api
Deploy : on publie
name: Build and Push Docker Image
on:
workflow_run:
workflows: ["Test Backend"]
types:
- completed
branches:
- main
jobs:
build-and-push-docker-image:
if: github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_branch == 'main' # Ensure deploy only runs for main
runs-on: ubuntu-22.04
steps:
# Checkout the code
- name: Checkout code
uses: actions/checkout@v4
# Log in to Docker Hub using the provided credentials
- name: Login to DockerHub
run: docker login -u ${{ secrets.DOCKERHUB_USERNAME }} -p ${{ secrets.DOCKERHUB_TOKEN }}
# Build the Docker image for the backend and push it to Docker Hub
- name: Build image and push backend
uses: docker/build-push-action@v3
with:
# Relative path to the place where source code with Dockerfile is located
context: ./simple-api
# Note: tags have to be all lower-case
tags: ${{ secrets.DOCKERHUB_USERNAME }}/tp-devops-simple-api:latest
# Build on feature branches, push only on main branch
push: ${{ github.ref == 'refs/heads/main' }}
# Build the Docker image for the database and push it to Docker Hub
- name: Build image and push database
uses: docker/build-push-action@v3
with:
context: ./database
tags: ${{ secrets.DOCKERHUB_USERNAME }}/tp-devops-database:latest
# Build on feature branches, push only on main branch
push: ${{ github.ref == 'refs/heads/main' }}
# Build the Docker image for the HTTP server and push it to Docker Hub
- name: Build image and push httpd
uses: docker/build-push-action@v3
with:
context: ./http-server
tags: ${{ secrets.DOCKERHUB_USERNAME }}/tp-devops-httpd:latest
# Build on feature branches, push only on main branch
push: ${{ github.ref == 'refs/heads/main' }}
# Build the Docker image for the front-end and push it to Docker Hub
- name: Build image and push frontend
uses: docker/build-push-action@v3
with:
context: ./devops-front
tags: ${{ secrets.DOCKERHUB_USERNAME }}/tp-devops-front:latest
# Build on feature branches, push only on main branch
push: ${{ github.ref == 'refs/heads/main' }}
2-3 For what purpose do we need to push docker images?
On pousse des images Docker pour simplifier le déploiement, rendre l’application accessible depuis n’importe quel serveur ou cloud et automatiser les mises à jour sans configuration complexe.
2-4 Document your quality gate configuration
mvn -B verify sonar:sonar -Dsonar.projectKey=devops-project12345 -Dsonar.organization=devops-sonar123 -Dsonar.host.url=https://sonarcloud.io -Dsonar.token=${{ secrets.SONAR_TOKEN }}
Cette commande exécute Maven pour tester et analyser le code avec SonarCloud :
mvn -B
→ Exécute Maven en mode "batch" (évite les interactions utilisateur).verify
→ Compile et teste le projet pour vérifier son bon fonctionnement.sonar:sonar
→ Lance l'analyse du code avec SonarCloud (vérification de la qualité du code, détection de bugs, etc.).-Dsonar.projectKey=devops-project12345
→ Identifie le projet dans SonarCloud.-Dsonar.organization=devops-sonar123
→ Spécifie l'organisation SonarCloud associée.-Dsonar.host.url=https://sonarcloud.io
→ Indique l’URL du serveur SonarCloud.-Dsonar.token=${{ secrets.SONAR_TOKEN }}
→ Utilise un jeton secret pour s’authentifier et envoyer les résultats sur SonarCloud.
Cette commande compile, teste et envoie l'analyse du code à SonarCloud pour évaluer sa qualité.
Connexion en ssh :
chmod 400 id_rsa
ssh -i id_rsa [email protected]
add dans /etc/ansible/hosts
, le dns doit marcher
emre.elma.takima.cloud
3-1 Document your inventory and base commands
Inventories/setup.yml
:
# Définition de l'inventaire Ansible
all:
vars:
# Utilisateur SSH pour se connecter aux hôtes
ansible_user: admin
# Chemin vers la clé privée SSH pour l'authentification
ansible_ssh_private_key_file: /home/wind/id_rsa
children:
# Groupe d'hôtes de production
prod:
# Liste des hôtes dans le groupe de production
hosts:
emre.elma.takima.cloud:
Commande ansible :
# To ping
ansible all -i ansible/inventories/setup.yml -m ping
# request your server to get your OS distribution
ansible all -i ansible/inventories/setup.yml -m setup -a "filter=ansible_distribution*"
# Install Apache into your instance to make it a webserver
$ ansible all -m apt -a "name=apache2 state=present" --private-key=id_rsa -u admin --become
# remove Apache
ansible all -i ansible/inventories/setup.yml -m apt -a "name=apache2 state=absent" --become
# Execute playbook, --syntex-check pour check avant de lancer
ansible-playbook -i ansible/inventories/setup.yml ansible/playbook.yml
3-2 Document your playbook
Playbook avec toutes les tâches dans playbook.yml
- hosts: all
gather_facts: true
become: true
roles:
- docker
tasks:
# Install prerequisites for Docker
- name: Install required packages
apt:
name:
- apt-transport-https
- ca-certificates
- curl
- gnupg
- lsb-release
- python3-venv
state: latest
update_cache: yes
# Add Docker’s official GPG key
- name: Add Docker GPG key
apt_key:
url: https://download.docker.com/linux/debian/gpg
state: present
# Set up the Docker stable repository
- name: Add Docker APT repository
apt_repository:
repo: "deb [arch=amd64] https://download.docker.com/linux/debian {{ ansible_facts['distribution_release'] }} stable"
state: present
update_cache: yes
# Install Docker
- name: Install Docker
apt:
name: docker-ce
state: present
# Install Python3 and pip3
- name: Install Python3 and pip3
apt:
name:
- python3
- python3-pip
state: present
# Create a virtual environment for Python packages
- name: Create a virtual environment for Docker SDK
command: python3 -m venv /opt/docker_venv
args:
creates: /opt/docker_venv # Only runs if this directory doesn’t exist
# Install Docker SDK for Python in the virtual environment
- name: Install Docker SDK for Python in virtual environment
command: /opt/docker_venv/bin/pip install docker
# Ensure Docker is running
- name: Make sure Docker is running
service:
name: docker
state: started
tags: docker
Notre playbook d'installation de docker est sympa et tout mais il sera plus propre à avoir à un endroit précis, dans un rôle par exemple. On ne garde que handlers
et tasks
.
Ajout du front aussi
On crée :
ansible-galaxy init ansible/roles/copy_env_file
ansible-galaxy init ansible/roles/create_network
ansible-galaxy init ansible/roles/launch_database
ansible-galaxy init ansible/roles/launch_app
ansible-galaxy init ansible/roles/launch_proxy
ansible-galaxy init ansible/roles/launch_front
3-3 Document your docker_container tasks configuration
La configuration est similaire, par exemple, rôle pour launch-proxy
:
# tasks file for ansible/roles/launch_proxy
- name: Launch Proxy Container
community.docker.docker_container:
# Nom du conteneur Docker
name: http-server
# Image Docker à utiliser pour le conteneur
image: wanoni/tp-devops-httpd:latest
# Toujours tirer la dernière version de l'image
pull: yes
# Réseaux Docker auxquels le conteneur sera connecté
networks:
- name: app-database-network
- name: app-front-network
# Ports à exposer sur le conteneur
ports:
- "80:80"
- "8080:8080"
httpd.conf
à modifier pour le front
# Écoute sur le port 8080
Listen 8080
# Configuration du VirtualHost pour le port 8080
<VirtualHost *:8080>
# Préserve l'hôte d'origine dans les requêtes proxy
ProxyPreserveHost On
# Redirige les requêtes vers le backend simple-api sur le port 8080
ProxyPass / http://simple-api:8080/
ProxyPassReverse / http://simple-api:8080/
</VirtualHost>
# Configuration du VirtualHost pour le port 80
<VirtualHost *:80>
# Préserve l'hôte d'origine dans les requêtes proxy
ProxyPreserveHost On
# Redirige les requêtes vers le frontend sur le port 80
ProxyPass / http://front:80/
ProxyPassReverse / http://front:80/
</VirtualHost>
# Charge le module proxy pour Apache
LoadModule proxy_module modules/mod_proxy.so
# Charge le module proxy_http pour Apache
LoadModule proxy_http_module modules/mod_proxy_http.so
On aura notre app et le front sur http://emre.elma.takima.cloud/departments
test-nackend.yml
se lance en 1er.
Une fois fini, build-push.yml
se lance.
Et finalement, deploy-production
se lance
# Nom du workflow
name: Deploy
# Déclencheurs du workflow
on:
workflow_run:
workflows: ["Build and Push Docker Image"] # Ce workflow s'exécute après la complétion du workflow "Build and Push Docker Image"
types:
- completed
branches:
- main # Ce workflow s'exécute uniquement sur la branche "main"
pull_request: # Ce workflow s'exécute également lors de la création de pull requests
# Définition des jobs
jobs:
deploy:
runs-on: ubuntu-latest # Le job s'exécute sur une machine virtuelle Ubuntu
steps:
- name: Checkout
uses: actions/checkout@v2 # Cette étape utilise l'action checkout pour récupérer le code source du dépôt
- name: Configure SSH
env:
ANSIBLE_PRIVATE_KEY: ${{ secrets.RSA_PRIVATE_KEY }} # Utilisation de la clé privée SSH stockée dans les secrets GitHub
run: |
echo "${ANSIBLE_PRIVATE_KEY}" > private_key.pem # Écriture de la clé privée dans un fichier
chmod 400 private_key.pem # Modification des permissions du fichier pour qu'il soit lisible uniquement par le propriétaire
mkdir -p ~/.ssh # Création du répertoire .ssh s'il n'existe pas
ssh-keyscan -t rsa,dsa emre.elma.takima.cloud >> ~/.ssh/known_hosts # Ajout de l'hôte distant à la liste des hôtes connus
mv private_key.pem ~/.ssh/id_rsa # Déplacement de la clé privée dans le répertoire .ssh
- name: Run Ansible Playbook
env:
ENV: ${{ secrets.ENV }} # Utilisation de la variable d'environnement stockée dans les secrets GitHub
run: |
echo "${ENV}" > env # Écriture de la variable d'environnement dans un fichier
chmod 400 env # Modification des permissions du fichier pour qu'il soit lisible uniquement par le propriétaire
ansible-playbook -i ansible/inventories/setup.yml ansible/playbook.yml # Exécution du playbook Ansible pour déployer l'application