Aller au contenu principal

TP 2 Dockerfile MyFirstApp

Objectif​

On va créer une image pour une micro webapp en Python.

Pour cela on va utiliser l'image Python basée sur Debian pour servir notre application Flask, en suivant les bonnes pratiques de sécurité et de qualité de code.

Les fichiers nécessaires sont en fin de page.

Étapes​

  • Action : PrĂ©parer le rĂ©pertoire local et les fichiers pour le projet Docker.
    Observation : Le répertoire contient les fichiers main.py, index.html et requirements.txt.
  • Action : CrĂ©er le fichier Dockerfile avec le code fourni.
    Observation : Le fichier Dockerfile est présent dans le répertoire et contient les instructions de base.
  • Action : Construire l'image Docker personnalisĂ©e.
    Observation : L'image est construite avec succĂšs sans erreurs de build.
  • Action : VĂ©rifier que l'image Docker créée est prĂ©sente dans la liste des images locales sur l'hĂŽte.
    Observation : L'image apparaĂźt dans la liste des images locales via docker images.
  • Action : Tagger l'image Docker avec un nom et une version appropriĂ©s.
    Observation : L'image est correctement taggée et identifiable.
  • Action : Tester l'image en lançant un conteneur.
    Observation : La webapp s'exécute correctement dans le conteneur.

Structure des fichiers du projet Python​

Voici l'arborescence simplifiée avec les fichiers essentiels :

MyFirstApp/
│
├── Dockerfile
├── main.py
├── templates/
│ └── index.html
└── requirements.txt

main.py

from flask import Flask, render_template, jsonify
import os
import redis

app = Flask(__name__)

# Configuration de Redis via les variables d'environnement
REDIS_HOST = os.getenv('REDIS_HOST', None)
REDIS_PORT = os.getenv('REDIS_PORT', 6379)

# Connexion Ă  Redis si disponible
redis_client = None
counter_value = 0 # Valeur par défaut si Redis n'est pas disponible
if REDIS_HOST:
try:
redis_client = redis.StrictRedis(host=REDIS_HOST, port=REDIS_PORT, decode_responses=True)
redis_client.ping()

# Initialiser le compteur à 0 si Redis est connecté et n'existe pas encore
if not redis_client.exists('counter'):
redis_client.set('counter', 0)
else:
# Lire la valeur actuelle du compteur depuis Redis
counter_value = redis_client.get('counter')
except redis.exceptions.ConnectionError:
redis_client = None

@app.route('/')
def index():
# Définir la variable pour savoir si Redis est disponible
redis_available = redis_client is not None
counter_value = redis_client.get('counter') if redis_available else 0
return render_template('index.html', redis_available=redis_available, counter=counter_value)


def check_all_dependencies():
return True


@app.route('/healthz')
def health_check():
if check_all_dependencies():
return 'OK', 200
else:
return 'Service Unavailable', 500

@app.route('/increment', methods=['POST'])
def increment_counter():
if redis_client:
# Incrémenter le compteur dans Redis
new_count = redis_client.incr('counter')
return jsonify({'status': 'success', 'new_count': new_count})
return jsonify({'status': 'error', 'message': 'Redis not available'})

if __name__ == "__main__":
app.run(host='0.0.0.0', port=3000)



templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MyFirstApp</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<h1>Welcome to MyFirstApp</h1>

{% if redis_available %}
<p>Redis is connected! Here are some options:</p>
<button id="increment-btn">Increment Counter</button>
<p>Counter: <span id="counter-value">{{ counter }}</span></p>
{% else %}
<p>Redisnot available yet. But you're getting there :).</p>
{% endif %}

<script>
$(document).ready(function() {
$('#increment-btn').on('click', function() {
$.ajax({
url: '/increment',
type: 'POST',
success: function(response) {
if (response.status === 'success') {
$('#counter-value').text(response.new_count);
} else {
alert(response.message);
}
},
error: function() {
alert('Error occurred while contacting the server.');
}
});
});
});
</script>
</body>
</html>


requirements.txt

Flask==2.3.2
redis==4.6.0

Dockerfile


ARG APP_VERSION=0.1.0
ARG IMAGE="python:3.11-slim-bullseye"
ARG APP_PATH="/app"

FROM $IMAGE

LABEL org.opencontainers.image.authors="support@mytechcompany.io" \
org.opencontainers.image.version="${APP_VERSION}" \
org.opencontainers.image.description="Docker image for MyFirstApp - a Flask application with optional Redis support"

WORKDIR $APP_PATH

RUN apt update && apt install -y curl && apt clean

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

RUN useradd -ms /bin/bash flaskuser && chown -R flaskuser $APP_PATH

USER flaskuser

COPY --chown flaskuser . .

EXPOSE 3000

CMD ["python","main.py"]

HEALTHCHECK --interval=30s --timeout=10s --retries=3 CMD curl -f http://localhost:3000/healthz || exit 1

Avancé​

  • Que se passe-t-il si vous relancez le build de l'image ?
  • Essayez de modifier le code HTML et de reconstruire l'image en changeant de version.
  • Comment lancer sur un autre port ? Peut-on lancer plusieurs fois la mĂȘme image ? Sur le mĂȘme port ?

Solution​

Afficher
  • CrĂ©er le rĂ©pertoire local : mkdir MyFirstApp && cd MyFirstApp
  • CrĂ©er les fichiers avec l'Ă©diteur de texte.
  • Ajouter les instructions au Dockerfile : voir le contenu fourni prĂ©cĂ©demment.
  • Construire l'image Docker : docker build .
  • VĂ©rifier la liste des images : docker images
  • Tagger l'image : docker tag [image_uuid] myfirstapp:1.0 ou docker build . -t myfirstapp:1.0
  • Lancer le conteneur : docker run --name myfirstapp -d myfirstapp:1.0
  • VĂ©rifier l'application : docker exec myfirstapp curl http://localhost:3000