Mise en contexte

La virtualisation

1 machines physique = plusieurs machines logiques

La virtualisation

✅ Avantages

  • Meilleurs utilisation des ressources
  • Possibilité d'avoir différents OS sur une machine
  • Simuler un environnement complexe

La virtualisation

😱 Inconvénients

  • Coûteux en ressources (2 OS)
  • Beaucoup de services inutiles dans un OS
  • C'est long !
  • Difficile à répliquer sur des postes de bureau

Meet Docker 🐳

  • Première version en 2013 (c'est assez récent)
  • À la base pour système UNIX, mais maintenant version windows (mais Linux > Mac > Windows)
  • Permet de résoudre les problèmes de la virtualisation
  • Principe d'infrastructure as code

In a nutshell

Les avantages ✅

  • Facile de créer une image
  • Une image se lance rapidement
  • Plus souple qu'une VM
  • Orienté 1 conteneur= 1 application
  • Système de couche

Les Inconvénients 😱

  • Demande un coût d'entrée
  • Rajoute de la complexité au métier de dev/data scientist
  • Volatilité et gestion du stockage

Les technos sous jacentes

  • Namespaces : permet de faire des processus dans les processus avec des identifiants identiques
  • cgroups : cloisonner les ressources (CPU, RAM, disque)
  • netfilter/iptable : réseau
  • Linux security module : gestion sécurité appels système

Hands-on 💻

  • Ouvrez 2 terminaux
  • Regarder les processus actifs avec ps -au
  • Exécutez la commande suivante dans un terminal
    
                                    sudo unshare -f -p --mount-proc usr/bin/sh
                                
  • Regarder les processus actifs avec ps -au dans les deux terminaux

Les couches dockers

Les couches dockers

Les couches dockers

  • Duplication d'info
  • Images artificiellement grosses
  • Problème de stockage et réseau

Les couches dockers

Les couches dockers

  • Mutualisation d'info
  • Images toujours aussi grosses
  • Mais possibilité d'utiliser des couches en local

Les couches dockers

Les couches dockers

  • Techniquement les systèmes de fichier s'additionne
  • Mais la couche peut altérer les couches du dessous (ajout/suppression/modification)

Hands on : mon premier conteneur🐳


                        sudo docker container run hello-world   
                    

Que s'est-il passé ?

  1. Verification si image en local
  2. Récupération de l'image (avec des couches)
  3. Lancement de l'image
  4. Arrêt du conteneur

Les bases de docker

Les commandes de base 1


                            #Lancer un conteneur
                            sudo docker container run [-it] [-p port_local:port_conteneur] [--name conteneurname] [image_name]
                            
                            #Voir les conteneurs
                            sudo docker container ps
                        
                            #Stopper un conteneur
                            sudo docker stop [id/name]

                            #Lancer une commande dans un conteneur
                            sudo docker exec [id/name] [commande]

                            #Rentrer dans un conteneur
                            sudo docker exec -it [id/name]  bash
                        

Hands on : des conteneurs plus poussés 🐳

  • Allez sur le site docker hub chercher une image ubuntu et lancez là. Lancez un shell python3 dans ce conteneur.
  • Allez chercher une image postgres, lancez-la et connectez vous à la base depuis l'outil de votre choix (shell, python, java, pgadmin etc)

Les volumes

  • Actuellement nos conteneurs sont volatiles
    • Logs applicative ?
    • Base de données ?
    • Échange de fichiers ?
  • Possible de faire pointer un dossier d'un conteneur vers un stockage externe
  • S'appelle un volume

                        sudo docker container un [-v host/path:conteneur/folder[:ro]] [image_name]
                        

Hands on : conteneurs et volumes 💽

  1. Récupérez le code d'un site statique (soit à vous, soit sur Moodle)
  2. Lancez un conteneurnginx avec un volume qui pointe vers votre site
    • Le dossier dans le conteneurest : /usr/share/nginx/html
    • Donnez uniquement les droits en lecture
    • Pensez à mapper le port 80 du conteneur
    • Allez voir la page 127.0.0.1:80

Créer ses images 🧱

Dockerfile, image et conteneur

  • Dockerfile : fichier pour dire comment créer une image (recette de cuisine) (~1ko)
  • Image : une application packagée pour docker (du Ko au Go)
  • conteneur: une instance en fonctionnement d'une image

Le Dockerfile

  • Suite d'instructions pour créer une image
  • Commence en réutilisant une image existante
  • Installation des dépendances nécessaires
  • Copies des fichiers de l'application
  • Lancer l'application

                            # Téléchargement de la dernière image python
                            FROM python:3.8.3-alpine
                            
                            # Création de variable d'environnement
                            ENV PYTHONDONTWRITEBYTECODE 1
                            ENV PYTHONUNBUFFERED 1

                            # Installation de package nécessaire à lxml
                            RUN apk add --no-cache --virtual .build-deps gcc libc-dev libxslt-dev && \
                                apk add --no-cache libxslt && \
                                pip install --no-cache-dir lxml>=3.5.0 && \
                                apk del .build-deps  
                                
                            # installation du fichier requirements.txt
                            COPY requirements.txt requirements.txt
                            RUN pip install -r requirements.txt
                            
                            COPY . .
                              
                            # Run l'application
                            CMD ["gunicorn", "mysite.wsgi" , "-b", "0.0.0.0:8000"]
                        

Du Dockerfile à l'image

Pour créer une image faire depuis le dossier contenant l'image

                            docker image build -t myimage:latest .
                        

Hands on : mon premier docker file 🐳🐘

  1. Créez une image docker dérivée de l'image de postgres contenant les données présentes sur Moodle (partie Initialization scripts de la doc docker Postgres)
  2. Lancez votre image
  3. Connectez vous au conteneur
  4. Vérifiez que l'image contient bien les données

Si vous êtes en avance faite la même chose avec une base mongodb

Hands on : docker et python 🐳🐍

  1. Récupérez le code python disponible sur Moodle
  2. Faites la fonctionner en local
  3. Mettez la dans un conteneurdocker
    1. Partez de l'image python:3-alpine
    2. Exécutez l'application au lancement du package avec CMD ou ENTRYPOINT
  4. Testez votre image

Hands on : docker et java 🐳☕

  1. Téléchargez l'application example de spring boot : https://github.com/spring-projects/spring-petclinic
  2. Faites la fonctionner en local
  3. Mettez la dans un conteneurdocker
    1. Partez de l'image openjdk:8-jdk-alpine
    2. Packagez l'application
    3. Copiez le jar
    4. Exécutez l'application au lancement du package avec CMD ou ENTRYPOINT
  4. Testez votre image

Les limites de dockers

  • Déployer une appli 3 tiers (front js, back java, bdd)
  • 3 images dockers, besoin de les déployer dans l'ordre bdd, back, front pour pouvoir faire les liens entre les conteneurs.
  • Besoin de faire les liens à la main

On aimerait une macro image de 3 conteneurs

Meet Docker Compose

Compose is a tool for defining and running multi-conteneurDocker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration. To learn more about all the features of Compose, see the list of features.

Docker + Docker compose

  1. Docker :
    • Fichiers Dockerfile
    • Plusieurs docker run à faire
    • Ou mettre tout dans un script
  2. Docker Compose
    • Fichiers Dockerfile / des images
    • Un fichier docker-compose.yml
    • Un docker-compose up à faire

Le fichier docker-compose

  • Fichier yml (clef/valeur)
  • Commence par la version de docker-compose
  • Puis la clef services et une liste de services (~conteneur)
  • Et les paramètres du conteneur (ce que l'on mettrais dans un dockerfile

Le fichier docker-compose demo

  • Commande docker
    
    docker run -d \
    --name mongodb \
    -p 27017:27017 \
    -e MONGO_INITDB_ROOT_USERNAME=hello \
    -e MONGO_INITDB_ROOT_PASSWORD=world \
    mongo
                                    
  • Equivalent docker-compose.yml
    
    version: "3.9"
    services:
      mongodb:
        image: mongo
        container_name: mongo
        ports:
          - 27017:27017
        environment:
          - MONGO_INITDB_ROOT_USERNAME=hello
          - MONGO_INITDB_ROOT_PASSWORD=world
                                    

Le fichier docker-compose demo

Pour créer un conteneur mongodb + mongo express


docker network create mongo_net
docker run -d \
--name mongodb \
--network mongo_net \
-p 27017:27017 \
-e MONGO_INITDB_ROOT_USERNAME=hello \
-e MONGO_INITDB_ROOT_PASSWORD=world \
mongo
docker run -d \
--network mongo_net \
-e ME_CONFIG_MONGODB_ADMINUSERNAME=hello \
-e ME_CONFIG_MONGODB_ADMINPASSWORD=world \
-e ME_CONFIG_MONGODB_SERVER=mongodb \
-p 8081:8081 \
mongo-express
                                

Le fichier docker-compose demo

L'équivalent docker-compose.yml


version: "3.9"
  services:
    mongodb:
      image: mongo
      container_name: mongo
      ports:
        - 27017:27017
      environment:
        - MONGO_INITDB_ROOT_USERNAME=hello
        - MONGO_INITDB_ROOT_PASSWORD=world
    mongo-express:
      image: mongo-express
      ports:
        - 8081:8081
      environment:
        - ME_CONFIG_MONGODB_ADMINUSERNAME: hello 
        - ME_CONFIG_MONGODB_ADMINPASSWORD: world 
        - ME_CONFIG_MONGODB_SERVER: mongodb
      depends_on:
        - mongodb
                        

Syntaxe du docker compose


                        version: "3.9"
                          services:
                            mongodb: # Nom du service 
                              image: mongo # Nom de l'image à utiliser
                              build : path/to/dockerfile # Si l'image n'est pas en local, on la construit   
                              container_name: mongo # le nom du conteneur --name
                              ports: # les ports -p
                              - 27017:27017
                              environment: # les variables d'env -e ou --env
                                MONGO_INITDB_ROOT_USERNAME: hello
                                MONGO_INITDB_ROOT_PASSWORD: world
                            mongo-express: # Un autre conteneur
                              image: mongo-express
                              ports:
                                - 8081:8081
                              environment:
                                ME_CONFIG_MONGODB_ADMINUSERNAME: hello 
                                ME_CONFIG_MONGODB_ADMINPASSWORD: world 
                                ME_CONFIG_MONGODB_SERVER: mongodb
                              depends_on: # Doit être lancé une fois que la liste est construite
                                - mongodb
                    

Syntaxe docker-compose


# Lancer un groupe de service en étant dans le dossier du docker-compose.yml
docker-compose [-f file] up

# Éteindre
docker-compose down

# Voir
docker-compose ps

# Exécuter commande
docker-compose exec [container name]
                    

Docker et les tests

Docker et les tests

  • Les applications d'aujourd'hui sont composées de plusieurs services
  • Comment tester de manière isolé une application complexe ?
  • Se baser sur des services fait exprès ? Bof bof
  • Créer des conteneurs, c'est mieux mais laborieux
  • Faire que notre code génère des conteneurs !

Testcontainer

  • Existe pour python et java
  • Permet de lancer un conteneurlors des tests
  • Et de surcharger la configuration
  • Permet d'avoir des tests vraiment unitaire

Testconteneur: dépendances



    org.testcontainers
    testcontainers
    1.16.2
    test


    org.testcontainers
    junit-jupiter
    1.16.2
    test

                        

Testconteneur: créer un container


@conteneur# Annotation pour laisser testconteneurgérer le cycle de vie
private static PostgreSQLContainer<?> postgresDB = 
    new PostgreSQLContainer<>("postgres:13.2") //nom de l'image
        .withDatabaseName("postgres") // passage de variable d'environnement
        .withUsername("postgres")
        .withPassword("secret");
                        

Testconteneur: mettre à jour la configuration


                            @Testcontainers
                            @SpringBootTest
                            @ContextConfiguration(initializers = {EmployeeServiceTest.Initializer.class})
                            class EmployeeServiceTest {
                                @Container
                                private static PostgreSQLContainer<?> postgresDB = 
                                    new PostgreSQLContainer<>("postgres:13.2") // nom de l'image
                                        .withDatabaseName("postgres") // passage de variable d'environnement
                                        .withUsername("postgres")
                                        .withPassword("secret");
                                static class Initializer
                                        implements ApplicationContextInitializer<ConfigurableApplicationContext> {
                                    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
                                        TestPropertyValues.of(
                                                "spring.datasource.url=" + postgresDB.getJdbcUrl(),
                                                "spring.datasource.username=" + postgresDB.getUsername(),
                                                "spring.datasource.password=" + postgresDB.getPassword(),
                                                "spring.jpa.hibernate.ddl-auto=create-drop" // pour initialiser la base
                                        ).applyTo(configurableApplicationContext.getEnvironment());
                                    }
                                }
                            

Testconteneur: Faire un test


                            @Test
                            void getEmployees() {
                                // GIVEN
                                // WHEN
                                Iterable<Employee> employees = employeeService.getEmployees();
                                // THEN
                                assertNotNull(employees);
                            }
                            

Testconteneur: Bilan

  • Décorrélation entre le service de stockage et le code
  • Uniquement besoin de définir un conteneur
  • Et de modifier la configuration de Spring Boot
  • Car il va tout faire lui même
  • On arrive à avoir rendre étanche nos tests