Thématiques principales

lundi 19 août 2019

Docker Compose

Nous rebondissons une nouvelle fois! Dans l’article précédent nous avions traité d’un exemple de déploiement de python dans docker [1] . Mais nous avions constaté que nous devions spécifier dans nos images des informations concernant le contexte d'exécution, entre autre le hostname de la machine hôte… et franchement c’est un peu dégueu...

Alors jusqu’à maintenant nous avons utilisé docker dans différents exemples ou soit nous voulions expliquer certains aspects de son fonctionnement soit parce que tout simplement c'était plus simple pour  traiter le sujet en question.

À chaque fois nous n’avions eu besoin que d’un container ce qui ne posait pas de problème sauf dans ce dernier exemple!

Du coup on va voir comment s’en défaire et ça va justement être l’occasion de parler un peu de docker-compose [2].

Docker compose est justement la pour faciliter l’orchestration et le lancement de container docker dans un contexte commun cohérent [24].

Dit comme ça, c’est pas très concret, alors prenons un exemple simple! Prenons le cas où vous souhaiteriez mettre en place un environnement d'intégration continu comportant une instance Jenkins [3], une instance Sonar [4] et une instance Artifactory [5].

Docker-compose pour l’IC

Faire cela avec docker-compose est très simple: il suffit de définir un fichier [6] docker-compose.yml contenant la description suivante:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
version: "3.2"
services:
  tc-artifactory:
    image: docker.bintray.io/jfrog/artifactory-oss:latest

  tc-sonar:
    image: sonarqube:7.9.1-community

  tc-jenkins:
    image: jenkins/jenkins:2.176.2-alpine

Pour exploiter ce fichier, il ne reste plus qu’à lancer la commande suivante (-d pour daemon):

1
$ docker-compose up -d

Avec un petit docker ps, on constate alors que nous avons bien nos trois instances.

1
2
3
4
5
$ docker ps
CONTAINER ID        IMAGE                                            COMMAND                  CREATED             STATUS              PORTS                 NAMES
eededc622db4        sonarqube:7.9.1-community                        "./bin/run.sh"           18 minutes ago      Up 3 seconds        9000/tcp              tc-ic-base_tc-sonar_1
ec55466c74dc        jenkins/jenkins:2.176.2-alpine                   "/sbin/tini -- /usr/…"   18 minutes ago      Up 2 seconds        8080/tcp, 50000/tcp   tc-ic-base_tc-jenkins_1
2c02dc1c451f        docker.bintray.io/jfrog/artifactory-oss:latest   "/entrypoint-artifac…"   18 minutes ago      Up 3 seconds        8081/tcp              tc-ic-base_tc-artifactory_1

Bon du coup on essaye d'accéder aux interfaces web de nos services? et … la ca ne marche pas… évidemment! nous avons bien lancé chacun des instances mais nous n’avons pas spécifié de port mapping.

Qu’est ce que le port mapping ? 

Alors ce qu’il faut comprendre  en premier lieu avant de parler port mapping c’est la manière dont docker-compose va isoler les containers. Pour cela, il va construire un réseau spécifique pour ces derniers à base de bridge [7] et d’IpTable [8]. Bon vous ne connaissez pas? ce n’est pas grave mais dans l’absolue, ce que docker fait c’est en quelque sorte l'équivalent de la virtualisation de machine mais appliqué au réseau. L'intérêt de cela également c’est que dans ce réseau, docker-compose va devoir être capable de retrouver les container en leur attribuant une IP et un hostname par défaut, celui du nom du service déclaré dans le fichier docker-compose.yml.

Donc on ne n’à pas de câble ethernet, ni de switch, ni de router mais c’est comme si! Et nos container vont s'exécuter dans un réseau privé où il vont pouvoir mener leur petite vie isolée du reste du monde (pour aller plus loin [9-10]).

Le port mapping consiste alors à rendre accessible le port d’un où plusieurs des containers directement sur la machine hôte comme si c'était elle qui exposer le service. Cela revient donc à utiliser le paramètre -p de la commande docker run.

Du coup notre conf docker-compose.yml devient:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
version: "3.2"
services:
 tc-artifactory:
   image: docker.bintray.io/jfrog/artifactory-oss:latest
   ports:
     - 8081:8081

  tc-sonar:
    image: sonarqube:7.9.1-community
    ports:
      - 9000:9000

  tc-jenkins:
    image: jenkins/jenkins:2.176.2-alpine
    ports:
      - 8080:8080
      - 50000:50000


Alors si vous vous demandez pourquoi ce port mapping, je vous invite à consulter la documentation des différents outils puisque finalement  cette configuration est fortement conditionner avec le fonctionnement de chacun d’eux. À noter que ici nous avons fait le choix de reporter les ports sur leur homologue direct, mais dans l’absolue, rien ne nous y oblige.

Voila alors pour faire bien et si vous avez jeté un oeil à la doc des différents outils, il faudrait compléter cette configuration  par l’utilisation de quelques montages disque. Ici l’idée est que aucun des containers n’utilise directement son filesystem pour stocker des fichiers de configurations, des clefs, où même des logs si ces derniers ne sont pas agrégé par un kibana etc….

Nous avons donc maintenant les bases pour appliquer cela à notre application ReST [11].

Docker-compose exemple avec ReST

Dans cet exemple où nous avions mis en oeuvre deux composant ReST, nous avions aussi tenté de faire un packaging avec docker [1]. Souvenez vous nous avions même du spécifier dans la conf de notre deuxième application le hostname de la machine hôte… ce que nous avions trouvé très mal!

Avec docker compose, normalement on devrait pouvoir s’affranchir de ce problème. Dans docker-compose, nos deux containers vont vivre dans un réseau isolé mais du coup elle vont aussi posséder leur propre hostname. Ces derniers étant conditionné par la configuration déclaré dans le docker-compose.

Du coup on peut alors piloter par configuration toute la partie déploiement du notre application (celle ci étant composé des deux containers) sans oublier de spécifier dans la conf applicative les hostnames que porteront les container

1
client = Client(host='http://rest-people:5001/humans')

On va donc déclarer donc deux services associés aux deux containers (on les appelle services car dans les fait, on pourrait associer à un service plusieurs container du même type via l’utilisation de la propriété scale… mais c’est une autre histoire)


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
version: '3'
services:

 rest-people:
   image: rest-people
   build:
     context: .
     dockerfile: Dockerfile_people

 rest-people-stat:
   image: rest-people-stat
   build:
     context: .
     dockerfile: Dockerfile_stat
   ports:
     - "5002:5002"


On spécifie, l’image produite et le build utilisant le dockerfile adéquat. on effectue un port mapping pour exposer sur la machine hôte 5002 et on lance.

Donc, si on va sur localhost:5002, grâce au port mapping, la requête http effectué sur la machine hôte est renvoyée sur le container rest-people-stat qui lui, va interroger l’autre container pour avoir les données.

Bon par contre on notera que fait comme ça on ne comprend pas forcément que d’une rest-people-stat écoute sur le port 5002 et de même que rest-people sur 5001.

Surtout si on se base sur les Dockerfile respectif. Alors pour cela il existe un moyen d'être plus clair, l’utilisation du mot clef EXPOSE.

EXPOSE

Attention, justement, j’en parle ici car il y a souvent une confusion entre ce mot clé et ce qu’il fait.

EXPOSE [12] n’expose rien !!! seul le port mapping le fait que quand on est à l'extérieur du réseau local virtuel dédié au container on puisse interroger un container spécifique. En dehors de cela, c’est impossible.

EXPOSE n’est qu’un flag permettant à la lecture du dockerfile de connaître les différents point d’entrée applicative du container et d'éventuellement si le besoin se fait sentir de faire un port mapping avec…. ce qui n’est pas forcement nécessaire.

Et si nos deux containers parviennent à se parler c’est juste parce qu'ils sont dans le même réseau.

Ainsi EXPOSE est juste un Flag à intérêt documentaire. Mais il importe de le préciser si vous souhaitez que les utilisateurs de vos images puissent les utiliser facilement.

Ici nous allons donc juste modifier nos Dockerfile pour ajouter ces précisions:
  • EXPOSE 5001
  • EXPOSE 5002
Et dans le docker-compose on peut reprendre ces informations mais elles resteront à titre indicative.

Dépendance entre container.

Autre point important, les dépendances entre les containers. Que se passerait il si le container stat avait démarré avant le container people et que ce dernier se crashe lamentablement?

Et bien le premier container aura démarré et fournira un service illusoire. Et même dans le cas où people n’est juste pas prêt, le service sera proposé mais impossible à fournir.

Pour pallier à cela, il est possible de définir des dépendances entre les services avec le mot clef depends_on [13].

1
2
3
4
5
6
7
8
9
 rest-people-stat:
   image: rest-people-stat
   build:
     context: .
     dockerfile: Dockerfile_stat
   ports:
     - "5002:5002"
   depends_on:
     - rest-people

Un peu de paramétrage

Enfin dernier point, il est souvent nécessaire de faire passer des données au contexte de construction et de démarrage de docker-compose ou d'exécution du container. Pour cela, il est possible d’utiliser deux mécanismes:
  • les variables d’environnement
  • les arguments
Les variables d’environnement [14-15] permettent en précisant un fichier .env à coté du docker-compose.yml d’injecter des données impactant l'exécution dans le contexte courant. Par exemple si l’un des container doit connaître lors de son exécution le hostname de la machine hôte ou la localisation d’un service externe, de credentials etc… (attention pas de secrets qui sont geré par un mot clef spécifique) Ces informations ne conditionnent pas le build des conteneurs mais juste leur exécution.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 rest-people-stat:
   image: rest-people-stat
   build:
     context: .
     dockerfile: Dockerfile_stat
   ports:
     - "5002:5002"
   restart: always
   depends_on:
     - rest-people
   environment:
     - APP_VARIABLE=${APP_VARIABLE}


( On notera l’utilisation dans cet exemple de la propriété restart permettant de donner une politique de redémarrage au conteneur si celui ci se crashe)

Les arguments [16] à l’inverse ont pour but d'être exploité lors de la construction de l’image docker (on est à un niveau méta). Cela va donc conditionner durablement l’utilisation de cette donnée dans le conteneur. Son utilisation peut être par exemple pour injecter le git hash ayant produit l’image.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 rest-people:
   image: rest-people
   build:
     context: .
     dockerfile: Dockerfile_people
     args:
       - gitcommithash=${gitHash}
   ports:
     - "5001:5001"
   expose:
     - "5001"
   restart: always


Injecter le hash de git dans le docker compose build avec la commande suivante:


1
$ export gitHash=$(git rev-parse HEAD);docker-compose build

Un peu plus loin

Nous venons de voir un exemple plutôt basique de l’utilisation de docker-compose avec un paramétrage.

Il faut savoir que docker-compose ne se limite pas à cela et est une marche obligé vers docker-swarm [17] qui lui va vraiment gérer de l’orchestration. Nous aurons l’occasion d’en parler ainsi que de son concurrent. En attendant sachez qu’il est déjà possible de faire pas mal de configuration supplémentaire pour le management de vos containers.

Entre autre par exemple, il est possible d’ajouter une politique de gestion du signal de vie de votre application, un healthcheck [18]. De même il est possible de préciser la politique d'agrégation des logs en utilisant par exemple un flux syslog [18] ou de flux json, fluentd, etc...

Nous ferons peut être de petits articles sur ces points mais la référence en la matière reste la documentation officielle.

Nous n’avons pas beaucoup parler non plus de la configuration du réseau par défaut [20] construit pas docker qui bien souvent n’est pas nécessaire. La encore nous y reviendrons peut être.

Enfin on peut aussi évoquer le montage de volumes [21], ou de montage en ram, mais si vous êtes déjà familier des systèmes unix, cela ne devrait pas être un problème, sinon intéressez vous d’abord à mount, fstab et tmpfs [22]!

Mais nous passerons forcément sur tout ces points tôt ou tard (et si vous voulez aller un  peu plus loin [23]).

Si vous souhaitez avec une alternative vous pouvez essayer Crane, on en parle ici [25].

Références: 

[1] https://un-est-tout-et-tout-est-un.blogspot.com/2019/08/python-dans-docker.html
[2] https://docs.docker.com/compose/
[3] https://hub.docker.com/r/jenkins/jenkins
[4] https://hub.docker.com/_/sonarqube
[5] https://www.jfrog.com/confluence/display/RTF/Installing+with+Docker
[6] https://docs.docker.com/compose/compose-file/
[7] https://www.commentcamarche.net/contents/609-equipements-reseau-le-pont-bridge
[8] https://www.howtogeek.com/177621/the-beginners-guide-to-iptables-the-linux-firewall/
[9] https://success.docker.com/article/networking
[10] https://docs.docker.com/config/containers/container-networking/
[11] https://un-est-tout-et-tout-est-un.blogspot.com/2019/08/rest-avec-python.html
[12] https://stackoverflow.com/questions/22111060/what-is-the-difference-between-expose-and-publish-in-docker
[13] https://stackoverflow.com/questions/35832095/difference-between-links-and-depends-on-in-docker-compose-yml
[14] https://docs.docker.com/compose/env-file/
[15] https://docs.docker.com/compose/environment-variables/
[16] https://docs.docker.com/compose/compose-file/#args
[17] https://docs.docker.com/engine/swarm/
[18] https://docs.docker.com/engine/reference/builder/#healthcheck
[19] https://docs.docker.com/config/containers/logging/configure/
[20] https://docs.docker.com/compose/compose-file/#network-configuration-reference
[21] https://docs.docker.com/storage/volumes/
[22] https://doc.ubuntu-fr.org/mount_fstab
[23] http://www.ageekslab.com/docker/docker4/
[24] https://web.leikir.io/docker-compose-un-outil-desormais-indispensable/
[25] https://blog.ippon.fr/2015/03/26/orchestration-de-containers-docker-docker-compose-et-crane/

vendredi 16 août 2019

Python dans docker

Nous avons dans un article précédent [1] proposer un exemple de mise en oeuvre  de deux applications python exploitant ou exposant une interface ReST.

Dans un second article [2],  nous avons également vu qu’il était possible de de packager et livrer des applications python avec divers outils comme setuptools[3] ou pyinstaller [4].

Nous avions cependant laisser une question en suspend concernant une dernière façon de déployer nos appli python. Ainsi, comme vous l’avez deviné, (le titre à tout spoiler…) nous allons dans ce présent article nous intéresser au packaging python dans des container docker.

Alors du coup après avoir lu l’article précédent, vous allez surement demander, mais pourquoi? quel est l'intérêt? et bien pour deux raisons:

  • la première est la devise de docker [5]: build once, run everywhere ce qui permet d’aller bien au delà des plateformes et des machines virtuelles
  • la seconde est que comme à la place de livrer un package ou des sources, on va livrer une image docker, alors peu importe ce que nous mettrons dans le conteneur, cela restera dans le conteneur sous notre responsabilité en minimisant complètement les interventions d’installations d’un utilisateur

La limitation est évidemment que nous ne nous intéresserons qu’aux applications basées sur le réseaux…. (WEB, CLI, etc)

Du coup pour illustrer cet article, je vous propose de reprendre l’exemple déjà traité précédemment [1] avec lequel nous proposons de déployer dans un ou plusieurs conteneur docker les différentes parties applicatives.

Pour cela nous allons donc utiliser une image de base de type alpine dans laquelle nous allons ensuite déployer ce que nous aurons décidé d’etre notre livrable. Ici, le plus simple, et de directement pousser nos sources dans l’image en s’assurant préalablement que python est bien installé ainsi que les dépendances de notre application.

On va donc définir deux Dockerfile en s’inspirant de [6]: Dockerfile_people et Dockerfile_stat respectivement pour chaque applications:


#Dockerfile_people:

FROM alpine:latest
RUN apk add --no-cache python3
RUN pip3 install setuptools Wheel
RUN pip3 install flask
COPY . /opt/
CMD cd /opt;python3 -m people



#Dockerfile_stat:

FROM alpine:latest
RUN apk add --no-cache python3
RUN pip3 install setuptools Wheel
RUN pip3 install flask python_http_client
COPY . /opt/
CMD cd /opt;python3 -m people_stat


Puis on construit nos images:


$ docker build -t people -f ./Dockerfile_people .
$ docker build -t people_stat -f ./Dockerfile_stat .


Et on les lances (en oubliant pas d’exposer leur ports d'écoutes respectifs 5001 et 5002 avec le paramètre -p)


$ docker run -it -p 5001:5001 people
$ docker run -it -p 5002:5002 people_stat


Et la ca marche! euh oui mais pas complètement… autant si on essaye d'accéder à l’interface http://localhost:5001/humans, cela fonctionne mais…. si ont tente http://localhost:5002/recall, la rien ne va plus!

Qu’est ce qui se passe?

C’est simple dans la configuration applicative réalisant les requêtes sur “recall”, nous avons dit à notre application d’aller chercher les informations sur localhost:5001/humans…. mais ce localhost…. c’est lui même! Ce que l’on voulait c'était qu'il aille sur l’autre container! Alors l’erreur semble évidente mais souvent on la fait car on développe sur un poste en local sans se préoccuper de l'aspect distribué du système que l’on est en train de faire… et donc je vous assure tout le monde tombera dans cette erreur.

Donc du coup on fait quoi?

C’est simple on ne met pas localhost mais le hostname de la machine qui expose réellement ce port! C’est à dire dans notre situation, la machine physique. (je vous laisse essayer ).

Alors bien sur généralement une information comme celle ci ne devra pas être mis en dur dans le code, on préférera s’appuyer sur des fichiers de configuration, accessibles depuis un volume ou définis par des variables d’environnement alimenté au moment du lancement du container. L’idée ici n’est pas d’aller jusque la mais déjà de voir ce que permet docker basiquement et certaines des limitations que cela induit, ici entre autre une adhérence résiduelle à la machine physique.

Nous verrons dans un prochain article comme avec docker toujours s’en affranchir.

Références:

[1] https://un-est-tout-et-tout-est-un.blogspot.com/2019/08/rest-avec-python.html
[2] https://un-est-tout-et-tout-est-un.blogspot.com/2019/08/deployer-du-python.html
[3] https://setuptools.readthedocs.io/en/latest/setuptools.html
[4] https://www.pyinstaller.org/
[5] https://uwm.edu/business/event/docker/
[6] https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xix-deployment-on-docker-containers

dimanche 11 août 2019

Déployer du Python

L’idée de cet article est de nous intéresser aux modes de livraison possible d’un programme python. L'enjeux est important car, en effet, écrire du code c’est bien mais sans livraison, c’est comme si ce code n’existait pas! Il faut donc avoir des moyens fiables et rapides permettant de fournir à un client son application python “clef en main”.

Via les sources

Dans cette approche ce qui sera fourni ce sera directement les sources qui seront mis à disposition de l’utilisateur final. Ce dernier aura alors la charge de fournir un environnement disposant non seulement de python dans la version adéquate mais aussi d’avoir pre-installer les modules dont l’application dépende.

Par exemple, sur une machine Debian, pour lancer l’application, il va être nécessaire préalablement de faire :

1
2
3
4
5
$ apt-get update && apt-get install -y --no-install-recommends python3 \
   python3-pip python3-dev \
$ apt-get clean && rm -rf /var/lib/apt/lists/
$ pip3 install --upgrade pip setuptools
$ pip3 install dependencies

Pour finalement pouvoir lancer l’application sur un appel du module :

1
$ python3 -m monAppli

Le point négatif de cet approche est la gestion des droits car lors du lancement de l’application, et à moins de préciser que l’on veille que python reste en mode interpréteur [pyt-pyc], différents fichiers vont être générés sur le poste cible. Il importe donc que ces fichiers puisse être généré sans être gêné par des permissions du systèmes mal prévu (et qui seront de toute façon délicat à mettre en oeuvre).

Via les fichiers pré-compilés

C’est une possibilité il faut l’avouer mais alors il ne sera pas possible de faire de patch ou de modification du code source sans compter que ce code sera globalement dispatcher dans un ensemble de fichiers et de répertoire (à l’image des sources).

Cela n'empêchera pas de devoir aussi préparer l’environnement d'exécution comme avec les sources. Donc c’est moins risqué coté permissions systèmes mais on perd un peu l'intérêt du python (le côté interpréteur)

À ce compte là, il serait alors mieux de simplifier la livraison en réalisant un package de l’application.

Exe, deb, rpm, etc...

Alors il est clair que faire un package de livraison, ca sera forcement en prenant en compte l’environnement de déploiement et il ne sera pas possible de déployer de la même manière sous windows où sous linux.

L'intérêt de l’approche est de fournir au client une seule référence et sera donc plus simple à gérer d’autant plus que selon la plateforme, cela permettre de bénéficier mécaniquement des avantages du mode de distribution (utilisation de aptitude et gestionnaire de paquet sous linux, pre-installation et configuration de paquets en dépendances, etc.

Pourtant les problèmes cités ci dessus seront toujours présents, code sources ou fichiers pre-compilés, il faudra gérer la dispersion de fichiers dans l’environnement.

On voit bien donc que le problème ici n’est pas le package de livraison, mais son contenu. Heureusement la communauté python à pensé à tout et à fournir différents outils pour livrer de façon cohérente notre application.

Pre-packaging

Ainsi pour réaliser ce pre-packaging, il existe quelques outils comme [setuptools], [distutils], [zipapp], [pyInstaller], py2exe (Windows) ou encore [cx_Freeze] (Windows et Linux) pour ne citer que les plus connu.

PyInstaller


Le plus simple à utiliser est PyInstaller, on l’installe et on l’applique sur le main:

1
2
$ pip install pyinstaller
$ pyinstaller monAppliv.py

et on obtient un répertoire dist dans nos sources contenant notre applications et toutes les librairies nécessaire à son exécution.

En voyant ca vous allez me dire, ok on a troqué les fichiers sources oi pre-compilé avec des librairies…. oui mais la il n’est pas nécessaire d’installer python sur le poste! Par contre attention, la construction qui à été réalisé à être spécifique à une plateforme… donc il faudra prévoir un build pyinstaller par plateforme cible.

setuptools


Setuptools est à l’inverse un package qui va permettre de construire un module versionable comme ceux télécharger lors de l’utilisation de pip install.

Setuptools repose sur distutils et je ne présenterai donc pas ce dernier qui est de plus bas niveau dans la construction des modules package python.

Setuptools est probablement l’outil le plus employé par la communauté car il permet l'intégration du module développé au sein d’un Server exploitable via pip. Ainsi même si python doit être préalablement installer sur le poste cible, la gestion des sources et fichier pre-compilés devient complètement transparent pour le développeur et le client.

De plus, contrairement au contraintes d’une gestion externe du packaging, setuptools, en gérant le versionning va faciliter via pip la mise à jour et l'évolution de l’application.

Pour cela il faut d’abord créer un descripteur de l’application et ce en python. Par exemple dans le cas de nos applications ReST [rest-py], on va créer un fichier setup.py contenant le code suivant:

1
2
3
4
5
6
7
8
from setuptools import setup, find_packages
setup(
    name="people",
    version="0.1.0",
    packages=find_packages(),
    scripts=['people.py','json_tk.py','human.py'],
    install_requires=['docutils>=0.3','Flask>=1.1.1']
)

Ensuite ce code sera exécuté bêtement comme un script :

1
$python setup.py sdist

Ce code va alors produire un targz de notre application contenant nos sources et déclarant les dépendances adéquates pour son installation.

Ainsi une fois packager il suffit alors de fournir ce package au client que celui ci exécute la commande suivante:

1
$ pip install people-0.1.0.tar.gz

Notre application est alors installée dans python (à cette occasion on préférera utiliser un virtualenv…) Et utilisable comme ceci:


1
$ python -m people

Dans cet exemple, avec setuptools, on voit que l’on est beaucoup moi adhérant à la plateforme mais que cela implique que python soit préalable installer dans celle-ci.

L’avantage sera évidemment sa mise à jour mais on notera que si ici on a créé un module setup.py, le nom de ce fichier est impératif sinon l’installation ne sera pas possible avec pip. du coup on notera que notre façon de faire nos applications dans l’exemple [rest-py] se prête finalement assez mal avec cette approche (à moins de séparer le code métier du code applicatif mais cela peut être laborieux)

Conclusions

Nous venons de passer en revue l’ensemble des manière de livrer et déployer une application python. Toutes ces façons de faire comportent leur lot d’avantages et d'inconvenants. On notera malgré tout que la communauté python n’est pas en reste de solution pour fournir des approches, il restera ensuite à la charge des développeurs de faire un choix selon le contexte et les contraintes.

Cependant, nous n’avons pas évoqué une dernière solution possible pour la livraison et le déploiement de nos applications python…. à votre avis???

Références:

[rest-py] https://un-est-tout-et-tout-est-un.blogspot.com/2019/08/rest-avec-python.html
[pyt-pyc] https://stackoverflow.com/questions/154443/how-to-avoid-pyc-files
[distutils] https://docs.python.org/fr/3/library/distutils.html
[pyinstaller] https://www.pyinstaller.org/
[zipapp] https://docs.python.org/fr/3/library/zipapp.html
[setuptools] https://setuptools.readthedocs.io/en/latest/setuptools.html
[cx-freeze] https://cx-freeze.readthedocs.io/en/latest/index.html

jeudi 8 août 2019

Docker registry

Quand on utilise docker, on en vient forcément un jour à se dire (et en fait ca arrive même très très vite), je veux publier les images que j’ai construit. Dans l’utilisation que l’on en fait quotidiennement, c’est à dire le cadre privé, l’utilisateur utilisera le docker hub.

Pourtant le docker hub est publique, c’est à dire que toutes les images que vous allez y déployer seront disponibles aux autres utilisateurs de docker.

C’est évidemment une approche qui se tient de favoriser par défaut la diffusion de images et de vouloir en faire profiter tout le monde dans un esprit de libre échange et bien sur il est quand même possible moyennant un loyer de disposer d’un espace privé dans le docker hub afin de rendre nos images accessibles qu’à un ensemble d’utilisateurs spécifiques ayant les permissions adéquates.

Pourtant, tout cela implique donc de laisser la responsabilité à docker hub de gérer l’accessibilité à nos données alors qu’en fait si le besoin de réutilisation des images est local et interne à l’entreprise, alors il devient légitime de vouloir avoir votre propre “docker hub” à la maison.

Alors sans aller sur un docker-hub avec toute l’interface graphique, ce qui importe c’est de disposer du logiciel qui va gérer nos images. Pour cela, comme le docker hub, il faut utiliser l’image registry [1] fourni par docker [2] [3].

Pour l’utiliser rien de compliqué: il suffit de lire la doc:

$ docker pull registry

Ensuite on lance un conteneur instance de cette image en exposant le port de l’API pour y accéder en prévoyant son redémarrage en l'état en montant un volume:


$ docker run -d -p 5000:5000 -v “/media/storage/registry-volume:/var/lib/registry:rslave” registry:2.7.1


Une fois le container lancer, il reste à tester la registry:

$ docker pull ubuntu
$ docker tag ubuntu localhost:5000/ubuntu
$ docker push localhost:5000/ubuntu

et c’est tout? oui pour déployer des images mais, comment les récupérer? et surtout savoir qu’elles existent?

C’est la que l’on va utiliser l’API [4] de la registry. Par exemple pour connaitre le contenu de notre registry on peut exécuter la requête ReST GET suivant:

$ curl -X GET http://localhost:5000/v2/_catalog

{"repositories":["artifactory-oss","hello-world","hw","jenkins","nginx","registry","sonarqube"]}

On obtient alors au format Json l’ensemble des images contenu dans la registry.

Pour en connaître ensuite le détail des tags existant pour une image, il va falloir élaborer une autre requête:


$ curl -X GET http://localhost:5000/v2/hello-world/tags/list

{"name":"hello-world","tags":["latest"]}


Il existe via cette API bien d’autres types de requêtes permettant de manipuler les informations contenues dans la registry et entre autre de les manipuler manuellement comment en faisant les push ou les pull avec la commande docker.

Pour en savoir plus la doc est la référence [4].

Références

[1] https://github.com/docker-library/docs/tree/master/registry
[2] https://docs.docker.com/registry/
[3] https://hub.docker.com/_/registry
[4] https://docs.docker.com/registry/spec/api/