Docker en pratique : Angular + Asp.NET Core + SQL Server
Introduction
L'intégration de Docker dans l'univers des technologies Microsoft ne fait que s'accentuer, et si la simplicité du démarrage sur Docker n'est plus à démontrer (cf. 5 minutes pour démarrer sur Docker avec Visual Studio 2017), voyons maintenant comment mettre en place une architecture un peu plus complète avec Visual Studio 2017.
L'architecture envisagée est cette fois-ci composée d'une web api aspnetcore, d'une base de données SQL Server 2017, ainsi que d'une application Angular 4 pour l'affichage graphique.
L'affichage se contente juste de présenter une simple liste de valeurs provenant de la base de données.
Le code source de l'application finalisé est disponible sur github à cette adresse : https://github.com/vfabing/docker-aspnetcore-mssql-sample
Étape 1 - Création et dockerisation d'une API aspnetcore
Cette première étape est probablement la plus simple. Il est possible de procéder de la même manière que décrit dans l'article de démarrage sur Docker, avec un ajout du support de Docker dès la création de l'API.
Il est également possible de rajouter à une web api existante les fichiers Docker nécessaires (i.e. les fichiers Dockerfile et docker-compose.yml) en faisant un clic droit sur le projet web et en sélectionnant Add >> Docker Support
Le fichier Dockerfile permet de décrire la manière dont est construit le conteneur Docker (L'image de base, où se situe la librairie aspnetcore à démarrer, etc.)
FROM microsoft/aspnetcore:2.0 ARG source WORKDIR /app EXPOSE 80 COPY ${source:-obj/Docker/publish} . ENTRYPOINT ["dotnet", "OMC.DockerApi.dll"]
Note: En mode de compilation Debug, Visual Studio construit une seule fois l'image du conteneur, et se sert de l'argument source pour que celui-ci utilise le package aspnetcore situé dans le répertoire obj/Docker/publish (présent en local et uniquement par référence dans le conteneur). Pour générer des images Docker avec les binaires embarqués, il est important de bien lancer la compilation en Release.
Les fichiers docker-compose.yml et docker-compose.override.yml permettent quant à eux de décrire l'infrastructure cible, décomposés en services (pour l'instant un seul service "omc.dockerapi" correspondant à l'api)
Étape 2 - Ajout d'une instance d'un conteneur SQL Server 2017 sur l'infrastructure Docker
Pour ajouter un conteneur sql server 2017 dans notre infrastructure, ce sont bien entendu les fichiers docker-compose.yml et/ou docker-compose.override.yml qu'il faut modifier.
À noter que le fichier docker-compose.override.yml permet de rajouter / écraser les valeurs contenues dans le fichier de base docker-compose.yml. Ce fichier est généralement utilisé lors du développement.
Dans l'exemple sur github, la description du service mssql est découpée dans ces 2 fichiers.
Une vision agrégée de ces 2 fichiers donne le résultat suivant :
services: omc.dockerapi: image: omc.dockerapi build: context: ./OMC.DockerApi dockerfile: Dockerfile depends_on: - mssql mssql: image: "microsoft/mssql-server-linux" expose: - "1433" environment: SA_PASSWORD: "YourStrong!Passw0rd" ACCEPT_EULA: "Y" MSSQL_PID: "Express" volumes: - .\mytestvolume:/var/opt/mssql ports: - "1433:1433"
Note: Pour des raisons de persistances de données (un conteneur est idéalement stateless), les données du serveur SQL sont stockées dans un sous dossier en dehors du conteneur, appelé mytestvolume
À noter également que l'utilisation de l'instruction "expose" permet d'exposer un port accessible uniquement depuis le réseau interne de notre infrastructure Docker, tandis que l'instruction "ports" permet d'exposer un port à l'extérieur de ce réseau (et donc accessible depuis notre poste en local par exemple) (cf commit)
Pour accéder depuis l'api au serveur SQL, il suffit d'utiliser le nom donné dans le fichier docker-compose au service SQL Server (mssql dans l'exemple sur github) dans n'importe quelle chaine de connexion. Exemple :
"Server=mssql;Database=ValuesDatabase;User=sa;Password=YourStrong!Passw0rd"
Docker se charge alors de résoudre le nom utilisé et de rediriger automatiquement vers le bon conteneur.
Étape 3 - Création et dockerisation d'une application Angular
Pour démarrer une application Angular classique, le plus simple reste encore de créer un nouveau projet via la ligne de commande ng init.
Note: Si besoin, ne pas hésiter à exécuter la ligne de commande ng serve pour vérifier que l'application ainsi créée fonctionne bien !
Une fois l'application Angular créée, 2 modifications sont nécessaires pour exécuter l'application Angular depuis un conteneur Docker :
- L'ajout d'un fichier Dockerfile à la racine décrivant la création de l'image Docker via copie des sources, exécution de npm install puis npm start.
FROM node:6 ARG source WORKDIR /app EXPOSE 4200 COPY ${source:-.} . RUN npm install ENTRYPOINT ["npm", "start"]
Note: Le fichier Dockerfile présent dans l'exemple Github est un peu plus complexe qu'une simple copie de fichier car il s'inspire du même système que le Dockerfile de l'api : Les packages Angular ne sont pas directement copiés dans conteneur mais y sont référencés pour éviter sa recréation à chaque compilation
- L'ajout de l'argument "--host 0.0.0.0" à la commande ng serve exécuté depuis l'instruction "start" du fichier json pour rendre accessible l'application angular sur n'importe quelle adresse IP
Ainsi configurée, l'application Angular est exécutable depuis un conteneur. Pour configurer son lancement automatique depuis le fichier docker-compose, il faut y rajouter un nouveau service comme par exemple :
services: angular: image: docker-angular build: context: ./docker-angular args: source: obj/Docker/empty/ depends_on: - omc.dockerapi volumes: - .\docker-angular:/app ports: - "4200:4200"
Note: Les instructions build:args: et volumes: sont utilisées pour passer les packages angular par référence plutôt que par copie directe. L'absence de ces 2 instructions permet de réellement copier les packages à l'intérieur du conteneur (c'est pourquoi elles sont idéalement à placer dans le fichier docker-compose.override.yml)
Étape 4 – Liaison entre l'application Angular et l'API aspnetcoreN
Pour finaliser notre système, il reste à rendre accessible l'API en dehors du réseau interne de nos conteneurs, ainsi qu'à ajouter un composant Angular connecté à l'API.
Le détail de cette mise en place est disponible sur ce commit github
Sur l'API, il est nécessaire de configurer les CORS pour permettre à l'application angular d'y accéder (app.UseCors() dans la méthode Configure du fichier Startup.cs), puis de l'exposer par exemple sur le port 8080 en modifiant l'instruction ports du fichier docker-compose :
ports: - "8080:80"
Il reste enfin à rajouter dans le fichier app.component.ts de l'application Angular les instructions pour récupérer les données depuis l'API :
import { Component } from '@angular/core'; import { Http } from '@angular/http'; import 'rxjs/add/operator/map'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'app works!'; API = 'http://localhost:8080'; values: any[] = []; constructor(private http: Http) { } ngOnInit() { this.getValues(); } getValues() { this.http.get(`${this.API}/api/values`) .map(res => res.json()) .subscribe(values => { console.log(values); this.values = values; }); } }
Conclusion
La compréhension de quelques concepts de bases est nécessaire pour bien démarrer sur Docker (Dockerfile, docker-compose.yml), mais une fois ceux-ci maitrisés, il est très simple de prendre n'importe quelle application pouvant être distribuée en tant que Service et de la porter dans un environnement Docker.
L'intégration des outils Docker dans Visual Studio permettent de simplifier son utilisation au quotidien et peuvent permettre, dans un premier temps, une prise en main en douceur du travail avec Docker.
Commentaires