Aller au contenu principal

Configuration d'un serveur HTTP Git dans Docker

Soumis par lulu le lun 20/02/2023 - 22:21

Pré-requis

  • Avoir installé Docker
  • Avoir installé Docker Compose

Configuration du projet

  • Créer un répertoire gitserver pour le projet, et à l'intérieur de celui-ci deux sous-répertoires :  repos (pour conserver les dépôts git hors du conteneur Docker) et etc (pour certains fichiers de configuration) :
$ cd ~/docker

#  -p, --parents     créer les répertoires parents nécessaires, sans erreur s'ils existent
$ mkdir -p gitserver/{repos,etc}

 

  • Dans le répertoire etc, on trouvera un fichier de configuration pour git (git.conf) et un script permettant la création de dépôts (create-repo.sh) ;
  • À la racine du répertoire gitserver, on trouve également deux fichiers : un Dockerfile (gitserver.Dockerfile) pour fabriquer le conteneur Docker, et un fichier yaml pour Docker Compose (docker-compose.yaml) ;
  • L'ensemble de la structure du répertoire gitserver est donc organisée comme suit :
Image adaptative

Le fichier gitserver.Dockerfile

# 20.04 LTS (Focal Fossa, 23 avril 2020)
FROM ubuntu:20.04

RUN apt update 2>/dev/null

# Debian, Ubuntu et leurs clones utilisent le système de configuration des paquets de Debian appelé "debconf".
# debconf peut présenter à l'utilisateur différentes interfaces en fonction de la variable d'environnement "DEBIAN_FRONTEND" :
# - non interactif : Vous utilisez ce mode lorsque vous avez besoin d'une interaction nulle lors de l'installation ou de la
#   mise à niveau du système via apt. Il accepte la réponse par défaut pour toutes les questions. Il peut envoyer un message
#   d'erreur à l'utilisateur root, mais c'est tout. Sinon, il est totalement silencieux et humble, un frontal parfait pour
#   les installations automatiques. On peut utiliser ce mode dans les fichiers Docker, les scripts shell, le script cloud-init, etc.
ENV DEBIAN_FRONTEND=noninteractive

# -y, --yes, --assume-yes : Répondre automatiquement oui aux questions - Présume « oui » comme réponse
#     à toutes les questions et s'exécute de manière non interactive
# -q, --quiet : Mode silencieux - Cette commande produit une sortie destinée à l'enregistrement
#     dans un fichier-journal en omettant les indicateurs de progression.
# vim n'est pas indispensable pour un serveur git, on l'installe par commodité.
RUN apt install -q -y git apache2 apache2-utils vim 2>/dev/null

RUN a2enmod env cgi alias rewrite
RUN mkdir /var/www/git

# -R, --recursive => opérer récursivement sur les fichiers et répertoires
# -f, --silent, --quiet  => supprimer la plupart des messages d'erreur
# -v, --verbose  => afficher un diagnostic pour chaque fichier traité
RUN chown -Rfv www-data:www-data /var/www/git

COPY ./etc/git.conf /etc/apache2/sites-available/git.conf
COPY ./etc/git-create-repo.sh /usr/bin/mkrepo

RUN chmod +x /usr/bin/mkrepo
RUN a2dissite 000-default.conf
RUN a2ensite git.conf

# utile pour la commande "git send-pack"
RUN git config --system http.receivepack true

# utile pour les commandes "git fetch-pack" et "git ls-remote"
RUN git config --system http.uploadpack true

ENV APACHE_RUN_USER=www-data
ENV APACHE_RUN_GROUP=www-data
ENV APACHE_LOG_DIR=/var/log/apache2
ENV APACHE_LOCK_DIR=/var/lock/apache2
ENV APACHE_PID_FILE=/var/run/apache2.pid

# "-D FOREGROUND" : indique que le serveur Apache doit être lancé au premier plan (foreground)
CMD /usr/sbin/apache2ctl -D FOREGROUND

EXPOSE 80/tcp

 

Rappel sur les instructions dans un fichier Dockerfile

ENV

ENV <key>=<value>
  • L'instruction ENV définit la variable d'environnement <key> à la valeur <value>. Cette valeur sera dans l'environnement pour toutes les instructions suivantes de l'étape de construction et peut être remplacée en ligne dans de nombreuses autres. La valeur sera interprétée pour d'autres variables d'environnement, donc les caractères 'quote' et 'doublequote' seront supprimés s'ils ne sont pas échappés. Comme pour l'analyse de la ligne de commande, les guillemets et les barres obliques inversées peuvent être utilisés pour inclure des espaces dans les valeurs.
  • Pour faciliter l'exécution de nouveaux logiciels, vous pouvez utiliser ENV pour mettre à jour la variable d'environnement PATH pour les logiciels installés par votre conteneur. Par exemple, ENV PATH=/usr/local/nginx/bin:$PATH garantit que CMD ["nginx"] fonctionne.
  • L'instruction ENV est également utile pour fournir les variables d'environnement requises spécifiques aux services que vous souhaitez conteneuriser, comme PGDATA de Postgres.
  • Enfin, ENV peut également être utilisée pour définir les numéros de version les plus courants afin de faciliter la maintenance des changements de version, comme le montre l'exemple suivant :

 

RUN

RUN a 2 formes :

# (forme shell, la commande est exécutée dans un shell, qui par défaut est /bin/sh -c sous Linux ou cmd /S /C sous Windows)
RUN <command> 

# (forme exec)
RUN ["executable", "param1", "param2"] 
  • L'instruction RUN exécute toutes les commandes dans un nouveau calque (layer) par-dessus l'image actuelle et valide les résultats. L'image validée résultante sera utilisée pour l'étape suivante du fichier Docker.
  • La superposition des instructions RUN et la génération de commits sont conformes aux concepts fondamentaux de Docker, où les commits sont peu coûteux et où les conteneurs peuvent être créés à partir de n'importe quel point de l'historique d'une image, un peu comme le contrôle des sources.
  • La forme exec permet d'éviter le mélange de chaînes de caractères shell et d'exécuter des commandes à l'aide d'une image de base qui ne contient pas l'exécutable shell spécifié.
  • Le shell par défaut de la forme shell peut être modifié à l'aide de la commande SHELL.

 

COPY

COPY a deux formes :

COPY [--chown=<user>:<group>] <src>... <dest>

# Cette dernière forme est requise pour les chemins contenant des espaces.
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

Remarque : La fonctionnalité --chown est uniquement prise en charge sur les Dockerfiles utilisés pour construire des conteneurs Linux, et ne fonctionnera pas sur les conteneurs Windows. Étant donné que les concepts de propriété des utilisateurs et des groupes ne se traduisent pas entre Linux et Windows, l'utilisation de /etc/passwd et /etc/group pour traduire les noms d'utilisateurs et de groupes en identifiants limite cette fonctionnalité aux conteneurs basés sur le système d'exploitation Linux.

  • L'instruction COPY copie de nouveaux fichiers ou répertoires depuis <src> et les ajoute au système de fichiers du conteneur au chemin <dest>.
  • Plusieurs ressources <src> peuvent être spécifiées mais les chemins des fichiers et des répertoires seront interprétés comme relatifs à la source du contexte de la construction.
  • Chaque <src> peut contenir des caractères génériques et la correspondance sera effectuée en utilisant les règles filepath.Match de Go. Par exemple :
  • Pour ajouter tous les fichiers commençant par "hom" :
COPY hom* /mydir/

Le <dest> est un chemin absolu, ou un chemin relatif à WORKDIR, dans lequel la source sera copiée à l'intérieur du conteneur de destination.

L'exemple ci-dessous utilise un chemin relatif, et ajoute "test.txt" à <WORKDIR>/relativeDir/ :

COPY test.txt relativeDir/

Alors que cet exemple utilise un chemin absolu, et ajoute "test.txt" à /absoluteDir/

COPY test.txt /absoluteDir/

 

CMD

L'instruction CMD a trois formes :

# (forme exec, c'est la forme préférée)
CMD ["executable", "param1", "param2"] 

# (comme paramètres par défaut de ENTRYPOINT)
CMD ["param1", "param2"] 

# (forme shell)
CMD command param1 param2 
  • Il ne peut y avoir qu'une seule instruction CMD dans un Dockerfile. Si vous listez plus d'une CMD, seule la dernière prendra effet.
  • L'objectif principal d'une CMD est de fournir des valeurs par défaut pour un conteneur en cours d'exécution. Ces valeurs par défaut peuvent inclure un exécutable, ou omettre l'exécutable, auquel cas vous devez également spécifier une instruction ENTRYPOINT.
  • Si la CMD est utilisée pour fournir des arguments par défaut pour l'instruction ENTRYPOINT, les instructions CMD et ENTRYPOINT doivent être spécifiées avec le format de tableau JSON.
  • Note : La forme exec est analysé comme un tableau JSON, ce qui signifie que vous devez utiliser des guillemets doubles (") autour des mots et non des guillemets simples (').
  • Contrairement à la forme shell, la forme exec n'invoque pas un shell de commande. Cela signifie que le traitement normal du shell ne se produit pas. Par exemple, CMD [ "echo", "$HOME" ] ne fera pas de substitution de variable sur $HOME. Si vous voulez un traitement de l'interpréteur de commandes, utilisez le formulaire de l'interpréteur de commandes ou exécutez directement un interpréteur de commandes, par exemple : CMD [ "sh", "-c", "echo $HOME" ]. Lorsque vous utilisez la forme exec et que vous exécutez directement un interpréteur de commandes, comme dans le cas de la forme shell, c'est l'interpréteur de commandes qui effectue l'expansion des variables d'environnement, et non docker.
  • Lorsqu'elle est utilisée dans les formes shell ou exec, l'instruction CMD définit la commande à exécuter lors de l'exécution de l'image.
  • Si vous utilisez la forme shell de l'instruction CMD, alors la <commande> sera exécutée dans /bin/sh -c :
FROM ubuntu
CMD echo "Ceci est un test." | wc -
  • Note : Ne confondez pas RUN et CMD. RUN exécute réellement une commande et commite le résultat ; CMD n'exécute rien au moment du build du conteneur, mais spécifie la commande prévue pour l'image.

 

EXPOSE

EXPOSE <port> [<port>/<protocole>...]
  • L'instruction EXPOSE informe Docker que le conteneur écoute sur les ports réseau spécifiés au moment de l'exécution. Vous pouvez spécifier si le port écoute sur TCP ou UDP, et la valeur par défaut est TCP si le protocole n'est pas spécifié.
  • L'instruction EXPOSE ne publie pas réellement le port. Elle fonctionne comme un type de documentation entre la personne qui construit l'image et la personne qui exécute le conteneur, sur les ports qui sont destinés à être publiés. Pour publier réellement le port lors de l'exécution du conteneur, utilisez l'indicateur -p sur docker run pour publier et mapper un ou plusieurs ports, ou l'indicateur -P pour publier tous les ports exposés et les mapper sur des ports d'ordre supérieur.
  • Par défaut, EXPOSE suppose TCP. Vous pouvez également spécifier UDP :
EXPOSE 80/udp
  • Pour exposer à la fois sur TCP et UDP, incluez deux lignes :
EXPOSE 80/tcp
EXPOSE 80/udp

 

Le fichier git.conf

Il s'agit du fichier de configuration Apache pour Git :

<VirtualHost *:80>
ServerAdmin webmaster@localhost
 
SetEnv GIT_PROJECT_ROOT /var/www/git
SetEnv GIT_HTTP_EXPORT_ALL
ScriptAlias / /usr/lib/git-core/git-http-backend/
 
Alias / /var/www/git
 
<Directory /usr/lib/git-core>
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
AllowOverride None
Require all granted
</Directory>
 
DocumentRoot /var/www/html
 
<Directory /var/www>
Options Indexes FollowSymLinks MultiViews
AllowOverride None
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
LogLevel warn
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

 

Le fichier git-create-repo.sh

Il s'agit d'un script shelle permettant de créer des dépôts git :

#!/bin/bash
 
GIT_DIR="/var/www/git"
REPO_NAME=$1
 
mkdir -p "${GIT_DIR}/${REPO_NAME}.git"
cd "${GIT_DIR}/${REPO_NAME}.git"
 
git init --bare &> /dev/null
touch git-daemon-export-ok
cp hooks/post-update.sample hooks/post-update
git update-server-info
chown -Rf www-data:www-data "${GIT_DIR}/${REPO_NAME}.git"
echo "Git repository '${REPO_NAME}' created in ${GIT_DIR}/${REPO_NAME}.git"

 

Le fichier docker-compose.yaml

version: "3.7"
services:
  git-server:
  container_name: mygitserver
    build:
      dockerfile: gitserver.Dockerfile
      context: .
    restart: always
    ports:
      - "8083:80"
    volumes:
      - ./repos:/var/www/git
    image: mygit:1.0

 

Construction de l'image Docker du serveur Git HTTP

Maintenant, pour construire l'image Docker du serveur Git HTTP, exécutez la commande suivante :

$ cd ~/docker/gitserver/
$ docker-compose build

La construction d'une image Docker personnalisée peut prendre un certain temps, au cours duquel les différentes étapes s'affichent à l'écran. Pour chaque instruction dans le Dockerfile, une couche supplémentaire (layer) identifiée par un SHA est créée :

Step 17/21 : ENV APACHE_LOG_DIR=/var/log/apache2
 ---> Running in c7c9b4bd44fb
Removing intermediate container c7c9b4bd44fb
 ---> dda2038e2201
Step 18/21 : ENV APACHE_LOCK_DIR=/var/lock/apache2
 ---> Running in 512dc0df848a
Removing intermediate container 512dc0df848a
 ---> 2a447b6cac80
Step 19/21 : ENV APACHE_PID_FILE=/var/run/apache2.pid
 ---> Running in 620f784fabb2
Removing intermediate container 620f784fabb2
 ---> 1a3f6fb72b4b
Step 20/21 : CMD /usr/sbin/apache2ctl -D FOREGROUND
 ---> Running in 03ac0213f831
Removing intermediate container 03ac0213f831
 ---> 522b910c75a6
Step 21/21 : EXPOSE 80/tcp
 ---> Running in 469313f32983
Removing intermediate container 469313f32983
 ---> f9fea6dae724
Successfully built f9fea6dae724
Successfully tagged mygit:1.0

À ce stade, l'image Docker devrait être construite :


gitserver $ docker image ls
REPOSITORY            TAG       IMAGE ID       CREATED         SIZE
mygit                 1.0       3bcaaedcae89   7 seconds ago   255MB

 

Chaque fois que vous apportez une modification à l'un des fichiers gitserver.Dockerfile, etc/git.conf, etc/git-create-repo.sh, vous devez reconstruire l'image Docker à l'aide de la commande docker-compose build.

 

Démarrage du serveur HTTP Git

Pour démarrer le service git-server, exécutez la commande suivante :

# -d, --detach  => Mode détaché : Exécuter les conteneurs en arrière-plan
$ docker-compose up -d

Le service git-server devrait démarrer en arrière-plan :

gitserver $ docker-compose up --detach
Creating mygitserver ... done

 

Pour voir comment les ports sont mappés, exécutez la commande docker-compose ps :

# Comme vous pouvez le constater, pour le conteneur mygitserver, le port 8083 de l'hôte Docker est mappé au port 80 du conteneur TCP.

gitserver $ docker-compose ps

   Name                  Command               State                  Ports                
-------------------------------------------------------------------------------------------
mygitserver   /bin/sh -c /usr/sbin/apach ...   Up      0.0.0.0:8083->80/tcp,:::8083->80/tcp

 

On peut également utiliser la commande docker container ls :


gitserver $ docker container ls

CONTAINER ID   IMAGE       COMMAND                  CREATED         STATUS         PORTS                                   NAMES
28b6bf8ae48c   mygit:1.0   "/bin/sh -c '/usr/sb…"   2 minutes ago   Up 2 minutes   0.0.0.0:8083->80/tcp, :::8083->80/tcp   mygitserver

 

Si l'on souhaite rentrer dans le conteneur, on peut exécuter la commande suivante :

# --interactive , -i  Keep STDIN open even if not attached
# --tty , -t  Allocate a pseudo-TTY
gitserver $ docker exec -it mygitserver "/bin/bash"
root@28b6bf8ae48c:/#

 

Utilisation du serveur Git

Pour créer un nouveau dépôt Git nommé fooproject sur le conteneur du serveur Git HTTP, exécutez la commande suivante :

gitserver $ docker-compose exec git-server mkrepo fooproject
Git repository 'fooproject' created in /var/www/git/fooproject.git

Si vous voulez accéder au serveur HTTP Git à partir d'autres ordinateurs sur votre réseau, vous devez connaître l'adresse IP de votre hôte Docker. Pour trouver cette adresse IP, nous utilisons la commande suivante :


$ ip address

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 2c:f0:5d:61:ab:c7 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.39/24 brd 192.168.0.255 scope global dynamic noprefixroute enp3s0
       valid_lft 41559sec preferred_lft 41559sec
    inet6 2a01:e0a:2b0:2d90:a760:f1ab:695:cbd2/64 scope global temporary dynamic
       valid_lft 86253sec preferred_lft 62740sec
    inet6 2a01:e0a:2b0:2d90:95ba:4209:fb9f:8f43/64 scope global dynamic mngtmpaddr noprefixroute
       valid_lft 86253sec preferred_lft 86253sec
    inet6 fe80::a64f:56af:74aa:d13f/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
3: wlp4s0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 3c:7c:3f:49:26:6a brd ff:ff:ff:ff:ff:ff

4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:3a:2a:58:83 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:3aff:fe2a:5883/64 scope link
       valid_lft forever preferred_lft forever

Dans le cas précédent, l'adresse IP de l'hôte Docker est 172.17.0.1.

Nous pouvons maintenant accéder aux dépôts Git sur le serveur en utilisant l'URL http://<docker-host-IP>:8083/<reponame>.git :

 

$ cd ~

# <docker-host-IP> est l'adresse IP de votre hôte Docker.
# <reponame> est le nom du dépôt Git dans le serveur HTTP Git.
# Pour cloner le dépôt Git "fooproject" à partir du serveur :
$ git clone http://172.17.0.1:8083/fooproject.git

Clonage dans 'fooproject'...
warning: Vous semblez avoir cloné un dépôt vide.

Comme vous pouvez le voir, le référentiel est cloné avec succès. Mais il est actuellement vide. Un nouveau répertoire fooproject/ a été créé.


Arrêter le serveur HTTP Git

Pour arrêter le service git-server, exécutez la commande suivante :

gitserver $ docker-compose down

Le service git-server doit maintenant être arrêté.