LAB -TCOM-2021
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Help
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    --- title: TP Docker tags: Assistants --- <!-- TODO: Choisir entre conteneurs et containers pour être logique tout du long --> ### Requirements: - avoir `docker` et `docker-compose` [Explications ici :)](https://blog.moulard.org/installer-docker/) - avoir testé son installation: - `docker run hello-world` (pas de `sudo` (c'est relou mais sécurisé)) - `docker-compose --version` # Le déroulement du TP Le TP va se dérouler en deux étapes, un peu comme les TPs de l'ING1 : il y aura une partie de cours/TD et un exercice à faire à la fin, à rendre avant le 8/11/2020 à 23h42. # Cours ## Docker ![](https://logos-download.com/wp-content/uploads/2016/09/Docker_logo.png) > Selon la firme de recherche sur l'industrie 451 Research, « Docker est un outil qui peut impacter une application et ses dépendances dans un conteneur isolé, qui pourra être exécuté sur n'importe quel serveur ». > > -- <cite>[wikipedia](https://fr.wikipedia.org/wiki/Docker_%28logiciel%29)</cite> ### Qu'est ce que docker et les conteneurs ? Docker est un outil permettant de faciliter le développement et le déploiement de conteneur. Un conteneur est une enveloppe virtuelle qui permet de distribuer une application avec tous les éléments dont elle a besoin pour fonctionner : fichiers sources, environnement d'exécution, librairies, outils et fichiers. Il est tout à fait possible de réaliser des conteneurs sans l'aide de docker ou autres outils de ce type. Les conteneurs sont une forme de virtualisations qui est intégrée au Kernel Linux. Contrairement aux `Machines Virtuelles` qui virtualisent un OS entier, les conteneurs vont utiliser l'OS sur lequel ils sont lancés afin de limiter l'impact sur les performances. Les applications à l'intérieur des conteneurs sont "sur" l'OS mais sont séparées du reste des processus par divers moyens comme les namespaces. Étant donné que les conteneurs contiennent tout ce qui est nécessaire pour leurs usages (en général une application), ils permettent de limiter le besoin de configuration nécessaire afin de faire tourner ladite application. En effet, il suffit de télécharger l'image, d'avoir un outil comme docker pour la lancer et rien de plus, contrairement à une application directement sur l'OS qui pourrait nécessiter l'installation de plusieurs dépendances. Ces dépendances sont souvent installées via un package-manager ce qui rend l'étape de configuration propre à un système et peu portable. Les conteneurs permettent donc de s'affranchir de toutes ces complications. Cela a aussi pour effet d'améliorer le temps de déploiement d'une application. ![](https://i.imgur.com/zNfaqy3.png) A noter que docker n'est pas le seul outil de ce type, on peut aussi citer `LXC` et `Podman` par exemple. ### Pourquoi faire de la conteneurisation ? (avec Docker dans notre exemple) <!-- - virtualisation - registery - versionning @sevan --> Les conteneurs sont principalement utilisés car ils permettent d'avoir un environnement reproductible. Cela permet de faciliter le développement et les tests d'une application. Dans la majorité des cas, le comportement sera identique que ce soit sur le PC du développeur ou sur le serveur de production. Cela permet d'éviter les "Mais ça marche sur ma machine" lorsqu'il y a un problème en production. ![](https://i.imgur.com/IJUGRcS.jpg) Un développeur peut créer une image docker (nous verrons comment par la suite), cette image sera la même sur son PC et sur la production. Une image est versionnée, il est donc facile de revenir en arrière s'il y a le moindre soucis. Cette image pourra ensuite être partagée via un `registry` cela permet de rendre la rendre (et donc une application) disponible pour tout le monde très facilement. De plus, un conteneur isole l'application du reste de l'OS, cela présente un avantage de sécurité si le conteneur est lancé avec les bons paramètres. Si un attaquant prend le contrôle de l'application, il ne pourra pas sortir du conteneur (sauf cas particulier), il ne pourra donc pas altérer les autres applications, ni le système. Cet aspect de visualisation permet aussi de faire tourner plusieurs applications similaires sur un même OS. Ce qui n'est pas trivial avec des applications directement installées sur l'OS. Par exemple, si l'on veut plusieurs base de données PGSQL, cela serait moins évident car l'application se bind sur un port donné (par défaut 5432), on ne peut pas lancer 2 fois l'application sans changer la configuration d'au moins l'une des deux, cela n'est pas très pratique. Avec les conteneurs, il est très simple de changer le port sur lequel l'application va se bind sans modifier la configuration de l'application. ### Comment faire du docker ? ![Cycle de vie d'un produit dans Docker](https://i.imgur.com/nfV8066.png) Dans cette partie du TP, vous allez apprendre à faire du docker: comment créer le fichier `Dockerfile` et l'utiliser. La première chose à savoir est que Docker utilise des fichiers appelés `Dockerfile` pour créer ses conteneurs. Ces `Dockerfile` sont composés d'une suite d'instructions/commandes qui correspondent à toutes les commandes que voudrait faire un utilisateur pour créer une image. Ces instructions sont de la forme: ```dockerfile INSTRUCTION argument ``` Lors du build de l'image, Docker va créer des couches(`layers`) pour chacune des instructions spécifiés. Ces couches vont permettre d'optimiser les prochains build. En effet, Docker ne va pas recréer les `layers` qui n'ont pas changés: par exemple, Docker ne va pas installer une dependence à chaque build, seulement lors du premier ou lorsque les dépendances ont changé (il est possible de le forcer avec `--no-cache`). D'autre part, si un layer remarque un changement, et donc une obsolescence de cache, les layers d'après vont aussi être rebuild. ![Schema de fonctionnement des layers](https://i.imgur.com/n2nptfV.png) Pour build une image Docker, il faut un fichier `dockerfile` et build: ```bash docker build . ``` Le `.` permet de donner à Docker l'environnement de build (AKA le dossier courant). On peut aussi build une image avec un Dockerfile n'ayant pas un nom conventionnel: `-f ./Dockerfile_with_a_special_name`. Il est recommandé de nommer les versions de nos images docker. Pour cela, on peut lui donner un nom (cf les `tags` vu avant) \o/: ```bash docker build -t my_awesome_image:version . ``` #### Dockerfile Explorons un peu les différentes instructions qui sont utiles. Vous pourrez trouver la [spec](https://docs.docker.com/engine/reference/builder/) online. ##### `FROM` Pour faire un `Dockerfile` valide, cette instruction est **obligatoire**. Une image docker a toujours besoin de partir d'un layer existant. Elle permet de connaitre l'image de base sur laquelle se situe la future image. (Vous pouvez trouver une liste des images de base disponible sur le [repository docker](https://hub.docker.com/search/?q=&type=image)). Et ça ressemble à ça: ```dockerfile FROM <image>[:<tag>] [AS <name>] ``` Essayons: - On va mettre dans un fichier `Dockerfile` ceci: ```dockerfile FROM ubuntu:19.04 ``` - Puis build l'image: ```bash docker build . ``` On obtient ça: ```bash > docker build . Sending build context to Docker daemon 2.048kB Step 1/1 : FROM ubuntu:19.04 ---> 9f3d7c446553 Successfully built 9f3d7c446553 ``` On peut observer que docker va "envoyer" le Dockerfile au daemon docker pour qu'il puisse build. Ce daemon va exécuter la seule étape, retourner le digest du layer et finir de build l'image. <!-- TODO: Parler de la taille d'une image--> ##### `RUN` C'est bien joli d'utiliser une image de base, mais comment spécifiez des instructions pendant le build me direz vous, c'est avec l'instruction `RUN` : Elle permet d'exécuter une commande shell lors du build. On peut utiliser la version "simple": ```dockerfile RUN <command> ``` Cela va exécuter la commande avec le shell par défaut de l'OS : `/bin/sh -c` pour Linux ou `cmd /S /C ` sur windows. On peut aussi utiliser la version "exec": ```dockerfile RUN ["executable", "param1", "param2", ...] ``` Tips: On peut utiliser des backslash `\` pour faire des `RUN` sur plusieurs lignes. Essayons: - On va repartir du fichier `Dockerfile` de la dernière fois: ```dockerfile FROM ubuntu:19.04 ``` - Et y ajouter une commande: ```dockerfile FROM ubuntu:19.04 RUN cat /etc/os-release ``` - Puis build l'image: ```bash docker build . ``` On obtient ça: ```bash > docker build . Sending build context to Docker daemon 2.048kB Step 1/2 : FROM ubuntu:19.04 ---> 9f3d7c446553 Step 2/2 : RUN cat /etc/os-release ---> Running in 41ff7a13ca27 NAME="Ubuntu" VERSION="19.04 (Disco Dingo)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 19.04" VERSION_ID="19.04" HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" VERSION_CODENAME=disco UBUNTU_CODENAME=disco Removing intermediate container 41ff7a13ca27 ---> 92b8b7f95f77 Successfully built 92b8b7f95f77 ``` On peut observer le caching des instructions en faisant un rebuild: ```bash > docker build . Sending build context to Docker daemon 2.048kB Step 1/2 : FROM ubuntu:19.04 ---> 9f3d7c446553 Step 2/2 : RUN cat /etc/os-release ---> Using cache ---> 92b8b7f95f77 Successfully built 92b8b7f95f77 ``` ##### `COPY`, `ADD` Maintenant qu'on peut exécuter des commandes shell dans le container, autant ne pas rester dans un monde fonctionnel pur et faisons des effets de bords sur des fichiers. ```dockerfile # ADD ADD <src>... <dest> ADD ["<src>",... "<dest>"] # COPY COPY <src>... <dest> COPY ["<src>",... "<dest>"] ``` La différence entre les deux est que `ADD` permet l'ajout de fichier depuis des sources différentes : `COPY` ajoute des fichiers ou dossiers alors que `ADD` peut aussi manipuler des fichiers distants (via URLs). Essayons de faire un hello world en python: - On va créer un ficher python basique: ```bash echo "print('hello world')" > app.py ``` - On va partir d'une image `python` pour le `Dockerfile`: ```dockerfile FROM python:3.8 COPY app.py . RUN python app.py ``` - Puis build l'image: ```bash docker build . ``` On obtient ça: ```bash > docker build . Sending build context to Docker daemon 5.12kB Step 1/3 : FROM python:3.8 ---> 28a4c88cdbbf Step 2/3 : COPY app.py . ---> f6db079cdc6c Step 3/3 : RUN python app.py ---> Running in 972191f3bec2 Hello, World! Removing intermediate container 972191f3bec2 ---> be8feedcb09d Successfully built be8feedcb09d ``` ##### `ENV` vs `ARG` Pour de la configuration plus flexible, Docker est friand des variables d'environnement. Ce sont des variables dont on peut avoir la valeur au runtime. Par exemple, on pourrait stoker en variable d'environnement le login de la base de données ou le numéro de port à bind. Dans le Docker file, ces variables sont indiquées avec la commande `ENV`: ```dockerfile ENV DB_PASS=super_secure_password ENV PORT=8080 ``` Ces variables vont aussi pouvoir être changées à l'utilisation du container. Tips: Pour des variables uniquement utiles au build de l'image, on peut utiliser `ARG` Essayons de faire un hello world en rust : - On va créer un ficher rust basique `app.rs`: ```rust use std::env; fn main() { match env::var("LANG") { Ok(lang) => { if lang.eq(&String::from("EN")) { println!("Hello World"); } else { println!("Bonjour Monde"); } } Err(e) => println!("Couldn't read LANG ({})", e), }; } ``` - On va partir d'une image `rust` pour le `Dockerfile`: ```dockerfile FROM rust:1.31 COPY app.rs . RUN rustc app.rs -o app ENV LANG=EN RUN ./app ``` - Puis build l'image: ```bash docker build . ``` On obtient ça: ```bash > docker build . Sending build context to Docker daemon 16.9kB Step 1/5 : FROM rust:1.31 ---> 6f61eb35ad91 Step 2/5 : COPY app.rs . ---> d5b1570b2c63 Step 3/5 : RUN rustc app.rs -o app ---> Running in e6354d1ff410 Removing intermediate container e6354d1ff410 ---> 24d81400a54e Step 4/5 : ENV LANG=EN ---> Running in 401cf76f6475 Removing intermediate container 401cf76f6475 ---> ac92e5f19357 Step 5/5 : RUN ./app ---> Running in 5d3c41a3a85e Hello World Removing intermediate container 5d3c41a3a85e ---> d8349d6d185c Successfully built d8349d6d185c ``` Et si on change la valeur de la variable, on obtient ça: ```bash > docker build . Sending build context to Docker daemon 16.9kB Step 1/5 : FROM rust:1.31 ---> 6f61eb35ad91 Step 2/5 : COPY app.rs . ---> Using cache ---> d5b1570b2c63 Step 3/5 : RUN rustc app.rs -o app ---> Using cache ---> 24d81400a54e Step 4/5 : ENV LANG=FR ---> Running in 59fc11b76b80 Removing intermediate container 59fc11b76b80 ---> 5535e8d40a4c Step 5/5 : RUN ./app ---> Running in 16d58e234947 Bonjour Monde Removing intermediate container 16d58e234947 ---> 44f72d0290f1 Successfully built 44f72d0290f1 ``` ##### `CMD` vs `ENTRYPOINT` Maintenant qu'on a pu compiler, on va lancer le binaire au démarrage du container. On verra comment lancer le container plus tard. Pour ça, on met: ```dockerfile ENTRYPOINT ["executable", "param1", "param2"] CMD <command> ``` `ENTRYPOINT` permet de définir un point d'entrée pour le container. Dans le cas où un `ENTRYPOINT` est défini, il est possible de définir un `CMD` afin de créer des options supplémentaires. Dans la pratique, `ENTRYPOINT` et `CMD` ont un rôle très similaire. Cependant, `ENTRYPOINT` est statique alors que `CMD` est dynamique. Il est alors possible de créer un `ENTRYPOINT` couplé à un `CMD` afin d'avoir une part d'arguments statiques et dynamiques. Attention, cette pratique n'est pas simple à gérer et demande de la pratique. Par exemple, pour le Reverse Proxy Traefik, son [`Dockerfile`](https://github.com/traefik/traefik/blob/master/Dockerfile) utilise `ENTRYPOINT` pour démarrer le binaire et `CMD` pour lui donner des paramètres. Essayons de faire un hello world en rust : - On va partir d'une image `ubuntu` pour le `Dockerfile`: ```dockerfile FROM ubuntu:19.04 ENTRYPOINT ["bash"] CMD ["--posix"] ``` - Puis build l'image: ```bash docker build . ``` On obtient ça: ```bash > docker build . Sending build context to Docker daemon 3.072kB Step 1/3 : FROM ubuntu:19.04 ---> 9f3d7c446553 Step 2/3 : ENTRYPOINT ["bash"] ---> Running in d7c726e2c46d Removing intermediate container d7c726e2c46d ---> ef0aacedc3fb Step 3/3 : CMD ["--posix"] ---> Running in 41fcaa66d49b Removing intermediate container 41fcaa66d49b ---> 439853250ab9 Successfully built 439853250ab9 ``` A noter que lors du run du container, l'instruction `CMD` va pouvoir pouvoir être remplacée. Alors que l'instruction `ENTRYPOINT` non. ##### `WORKDIR` On a réussi à lancer des commandes, mais on a jamais spécifié d'où on partait. On va maintenant utiliser WORKDIR pour le faire. ```dockerfile # WORKDIR WORKDIR /path/to/workdir ``` Un exemple de Dockerfile : ```dockerfile= FROM ubuntu:19.04 WORKDIR a WORKDIR b WORKDIR c RUN pwd WORKDIR /d/e/f RUN pwd ``` Et sa sortie : ```bash > docker build . docker build . Sending build context to Docker daemon 3.072kB Step 1/7 : FROM ubuntu:19.04 ---> 9f3d7c446553 Step 2/7 : WORKDIR a ---> Running in 862c9c25b001 Removing intermediate container 862c9c25b001 ---> b5182f87efaf Step 3/7 : WORKDIR b ---> Running in a025044916fb Removing intermediate container a025044916fb ---> 00639a0c768a Step 4/7 : WORKDIR c ---> Running in f7636684c808 Removing intermediate container f7636684c808 ---> c443fe035762 Step 5/7 : RUN pwd ---> Running in 666de2796715 /a/b/c Removing intermediate container 666de2796715 ---> 933bc24f7abd Step 6/7 : WORKDIR /d/e/f ---> Running in 6a73fa4b4b1a Removing intermediate container 6a73fa4b4b1a ---> 81d89d1f7712 Step 7/7 : RUN pwd ---> Running in df4d54248466 /d/e/f Removing intermediate container df4d54248466 ---> cd3537b0b0a7 Successfully built cd3537b0b0a7 ``` ##### `EXPOSE` Les étapes précédentes vous ont permis de créer un Dockerfile et de faire un docker pouvant faire tourner une application. Il faut maintenant laisser les utilisateurs accéder à votre application. Pour cela, on utilise la commande EXPOSE. En Dockerfile cela se traduit simplement : ```dockerfile # EXPOSE EXPOSE <port> [<port>/<protocol>...] #En pratique EXPOSE 80/tcp ``` ```dockerfile= FROM python:3.8 EXPOSE 8000 CMD python -m http.server 8000 ``` Avec ce Dockerfile, le docker aura un port ouvert qu'il faudra ensuite bind à un de vos ports. Cela sera vu et expliqué plus tard. On obtient ça: ```bash > docker build . Sending build context to Docker daemon 3.072kB Step 1/3 : FROM python:3.8 ---> 28a4c88cdbbf Step 2/3 : EXPOSE 8000 ---> Running in 74ed5b622239 Removing intermediate container 74ed5b622239 ---> 7eeb65ba6434 Step 3/3 : CMD python -m http.server 8000 ---> Running in c3d9cee464b8 Removing intermediate container c3d9cee464b8 ---> c82182474b7f Successfully built c82182474b7f ``` ##### Aller plus loin - [`HEALTHCHECK`](https://docs.docker.com/engine/reference/builder/#healthcheck) - [`USER`](https://docs.docker.com/engine/reference/builder/#user) - [`.dockerignore`](https://docs.docker.com/engine/reference/builder/#dockerignore-file) - multi layer images: On peut faire des images plus légère en utilisant des images temporaires pour compiler les binaires: ```dockerfile # image pour compiler le go FROM golang:1.13.4 AS builder # ... RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . # image plus légére pour run le go FROM scratch COPY --from=builder app . # .... ``` #### CLI Après la création de `Dockerfile`, utilisons les vraiment ! ##### `build` ```bash docker build [OPTIONS] PATH | URL | - ``` La commande build de Docker permet de créer une image qui va ensuite pouvoir être lancée et transformée en conteneur. Quelques options utiles : - `-t <name>` permet de donner un nom à l'image créée (tag) - `-f <path>` permet de spécifier un Dockerfile - `--no-cache` permet de build sans utiliser le cache de docker Pour en savoir plus sur les options de build : [Spec](https://docs.docker.com/engine/reference/commandline/build/) Essayons avec l'image que nous avons fait ##### `tag` ```bash docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG] ``` Docker tag permet de donner un nom à une image afin de la retrouver plus facilement. Une image qui a été build ou une image modifiée peut alors être nommée facilement. On se sert souvent de cette command en entreprise pour s'y retrouver parmi les différentes images créées. Les Tags sont composés de 4 parties: - `registery` : correspond au serveur d'images à utiliser(comme [docker.io](hub.docker.io), [goharbor.io](https://goharbor.io/)) - `author` : C'est l'auteur de l'image. Si l'auteur est `_` (c'est-à-dire Docker), il peut être omis. Par exemple, l'image `nginx` qui peut être utilisée dans auteur. - `name` : C'est le nom de l'image : `wordpress` par exemple. - `tag` : (oui, le tag du tag) permet de connaitre la version de l'image. Le tag par défaut est `latest`. Pour les images qui sont associées à : - des langages, comme l'image [haskell](https://hub.docker.com/_/haskell?tab=tags), le tag correspond à la version du langage : `haskell:8.8.4` - des services, comme l'image [gitlab-runner](https://hub.docker.com/r/gitlab/gitlab-runner/tags), le tag correspond à la version du service et à la version des layers sous-jacents : `gitlab/gitlab-runner:alpine-bleeding`, `gitlab/gitlab-runner:alpine-v13.5.0-rc2`, `gitlab/gitlab-runner:ubuntu-v13.4.0-rc1` Une liste non exhaustive de tags valides: - `docker.io/michel/awesomeapp:latest` - `michel/awesomeapp` - `mysql` ```bash # Depuis un ID de votre image locale > docker tag 0e5574283393 image/httpd:version1.0 # Depuis une autre image locale nommée > docker tag my-image epita/my-image:version1.0 # Depuis une image et un tag > docker tag my-image:beta0.1 epita/my-image:version1.0.test ``` ##### `push` ```bash docker push [OPTIONS] NAME[:TAG] ``` Cette commande permet de mettre en ligne dans le répertoire d'image de votre choix. Cela peut être le DockerHub ou une registry privée. ```bash # On push une rhel (image red hat) dans une registry > docker push registry-host:5000/myadmin/rhel-httpd ``` Tip: Les registry utilisent des comptes pour repérer les images, il faut donc faire un `docker login` pour se connecter et pouvoir push l'image voulue: ```bash docker login [OPTIONS] [SERVER] ``` ##### `run` ```bash docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...] ``` Docker run permet de lancer des conteneurs à partir d'une image. ``` bash # Exemple simple docker run > docker run -d -p 80:80 my_image service nginx start ``` Expliquons maintenant quelques options utiles : - `-p` permet de bind un port du conteneur à un des ports de votre PC - `-d` permet de lancer le conteneur en mode détaché (en gros vous le lancez en background) - `-it` afin d'avoir des process interactifs (et du coup lancer des commandes en plus dans votre docker) - `-e` définit des variables d'environnement dans le container - `-v` mount un volume dans le container: permet d'avoir de la persistance de données - `--name` donne un nom au container - `--rm` supprime le container au moment où il ne fonctionne plus Un autre exemple d'utilisation du run pour avoir un container Ubuntu pour faire des test: ```bash docker run -it --rm ubuntu bash ``` Si on ferme le tty de ce container (ctrl-d ou `exit`), il sera supprimé ; vous pouvez essayer de le casser ;) ##### `exec` ``` bash docker exec [OPTIONS] CONTAINER COMMAND [ARG...] ``` Docker exec permet de lancer des commandes dans des conteneurs existant. Avec Docker exec, il est possible de rentrer dans le conteneur afin de faire du debug (car comme tout épitéen normalement constitué, vous n'êtes pas des dieux du code). Quelques exemples de Docker exec : ``` bash # Afficher des informations internes au docker > docker exec -ti my_container sh -c "echo a && echo b" # Entrer dans le conteneur > docker exec -ti ubuntu bash ``` ##### `ps` ```bash docker ps [OPTIONS] ``` Docker ps permet de lister les conteneurs déployés ```bash # Les conteneurs qui tournent > docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2db9bb3bc2ea ubuntu:latest "/bin/bash" 8 months ago Up 9 hours Ubuntrash # tous les conteneurs (même ceux à l'arret) > docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 45866acdddd6 ubuntu "bash" 13 minutes ago Exited (0) 12 minutes ago nifty_aryabhata 2db9bb3bc2ea ubuntu:latest "/bin/bash" 8 months ago Up 9 hours Ubuntrash ``` Plus d'options disponibles ici : [link](https://docs.docker.com/engine/reference/commandline/ps/) Tip: Pour arrêter tous les container qui sont en train de tourner, on peut faire : `docker stop $(docker ps -q)` ##### `stop` ```bash docker stop [OPTIONS] CONTAINER [CONTAINER...] ``` Cette commande va envoyer un `SIGTERM` au process du container puis un `SIGKILL` après une période de grâce pour lui laisser le temps de s'éteindre. ##### network ```bash docker network COMMAND docker network create [OPTIONS] NETWORK docker network connect [OPTIONS] NETWORK CONTAINER docker network disconnect [OPTIONS] NETWORK CONTAINER docker network inspect [OPTIONS] NETWORK [NETWORK...] docker network ls [OPTIONS] docker network rm NETWORK [NETWORK...] ``` Vous êtes des malades si vous utilisez ces commandes à la main ! Mais les commandes sont suffisamment simples pour comprendre ce qu'elles font: - `create` permet de créer un network - `connect` permet de connecter un container à un network - `disconnect` permet de déconnecter un container à un network - `inspect` permet d'inspecter un network pour avoir des informations dessus - `ls` permet de lister les networks - `rm` permet de supprimer un/des network(s). ## docker-compose Woaw, vous avez atteint la moité de la partie cours du TP \o/ Vous savez maintenant utiliser docker (théoriquement) avec plein d'options. ### Pourquoi faire du docker-compose ? Mais utiliser docker en CLI c'est gentil 2 minutes (qui compile son C sans un `Makefile` ?). `docker-compose` est une sur-couche de docker qui permet de mettre dans un fichier tous les arguments qu'on aurait donné à docker. Mais aussi de gérer de multiples conteneurs en simultané plus facilement. Le but est donc de transformer un : ```bash= docker run \ --name=transmission \ -e PUID=1000 \ -e PGID=1000 \ -e TZ=Europe/London \ -e TRANSMISSION_WEB_HOME=/combustion-release/ `#optional` \ -e USER=username `#optional` \ -e PASS=password `#optional` \ -p 9091:9091 \ -p 51413:51413 \ -p 51413:51413/udp \ -v <path to data>:/config \ -v <path to downloads>:/downloads \ -v <path to watch folder>:/watch \ --restart unless-stopped \ linuxserver/transmission ``` en : ```bash docker-compose up ``` Ca vous parait magique ? :mage: Alors apprenons tous ensemble comment ça marche ! ### Comment faire du docker-compose ? Encore une fois, la [spec](https://docs.docker.com/compose/compose-file/) est disponible en ligne. #### docker-compose.yml Comme pour utiliser docker, docker-compose utilise des fichiers `docker-compose.yml` avec un formal [yaml](https://yaml.org/) ([wiki](https://fr.wikipedia.org/wiki/YAML)). Il sont composés de plusieurs parties qui décrivent des requirements à match: - Des services (expliqué plus loin) - Des networks pour lier de containers - Des volumes pour garder des fichiers persistants - Des secrets (peut utilisés) pour avoir des données chiffrée - Soon des imports ##### version Pour chaque version de docker-compose, la version que le programme va utiliser va changer. En effet, la version `'1'` n'est plus utilisée, la `'2'` continue d'être utilisée et la `'3'` est la plus complète. Pour spécifier la version, il suffit de faire: ```yml version: '3.6' ``` Si notre fichier `docker-compose.yml` ne contient que le numéro de version, il ne se passe rien lorsqu'on l'utilise: - On met le numéro de version dans un fichier `docker-compose.yml`: ```yml version: '3.6' ``` - On exécute la commande pour savoir si tout s'est bien lancé: ```bash > docker-compose up Attaching to > ``` `docker-compose up` permet de lancer les services décrits dans le fichier `docker-compose.yml`, mais on en parlera plus tard. ##### networks Les networks permettent de mettre des container sur un même réseau virtuel (par défaut, les networks sont en mode bridge). La spécification est assez simple, il faut le spécifier: ```yml networks: network-name: ``` Les valeurs par défaut suffisent amplement pour toute utilisation normale de docker-compose. (La doc est disponible pour les gens hardcore qui veulent allez plus loin) Pour essayer: - On peut partir du fichier `docker-compose.yml` du dernier exemple: ```yml version: '3.6' ``` - Et lui ajouter un network: ```yml version: '3.6' networks: network-name: ``` - On execute: ```bash > docker-compose up WARNING: Some networks were defined but are not used by any service: network-name Attaching to > ``` - On réfléchit: Vu que nous n'avons pas encore mis de services (oui, ça arrive, ça arrive), docker-compose ne peut pas faire un lien entre les containers et les networks, c'est pour ça qu'il indique une erreur. ##### volumes Pour conserver des fichiers entre deux runs de containers, on peut créer des volumes. Il y a deux moyens de créer des volumes: - En utilisant des dossiers et des fichier locaux (a faire directement dans le service) - En utilisant un file system virtuel Pour cela, on peut faire: ```yml volumes: volume-name: external: false ``` Sachant que l'on peut utiliser des file system distants, mais c'est pas fou. ##### services Voilà enfin la partie intéressante du fichier docker-compose, et là où on va passer le plus de temps. Le service va donc transformer une image docker en container. Il va décrire à docker-compose (et donc docker) comment run le container. Un fichier `docker-compose.yml` va donc se composer de plusieurs parties: ```yml version: '3.6' networks: network-toto: volumes: volume-tata: services: mon-service_1: ... mon-service_2: ... ``` ###### image C'est là qu'on spécifie le nom de l'image à utiliser pour le service. Ce nom va servir à la fois si on veut pull l'image et si on veut la build localement. Cet argument à la même forme que lorsqu'on a fait des `FROM` dans le Dockerfile: `[repo/][user/]<nom>[:tag]`. On peut donc faire: - Partir du `docker-compose.yml` précédent: ```yml version: '3.6' networks: network-name: ``` - Et lui ajouter un service avec un nom d'image: ```yml version: '3.6' networks: network-name: services: swagger: image: swaggerapi/swagger-ui ``` - Et on exécute: ```bash > docker-compose up WARNING: Some networks were defined but are not used by any service: network-name Creating network "demo_default" with the default driver Pulling swagg (swaggerapi/swagger-ui:)... latest: Pulling from swaggerapi/swagger-ui 188c0c94c7c5: Pull complete 61c2c0635c35: Pull complete 378d0a9d4d5f: Pull complete 2fe865f77305: Pull complete b92535839843: Pull complete 5a845b721bd5: Pull complete 11a7ab608318: Pull complete 2940893bd159: Pull complete 6e4cbe1b9035: Pull complete 9f938b7a4eb0: Pull complete d58a47b6172e: Pull complete Digest: sha256:17c0001f808d76c6833e6f33b965ceb1aa4955a277eb826e0eb3369eb54aae91 Status: Downloaded newer image for swaggerapi/swagger-ui:latest Creating demo_swagg_1 ... done Attaching to demo_swagg_1 ^CGracefully stopping... (press Ctrl+C again to force) Stopping demo_swagg_1 ... done ``` En analysant, on peut remarquer que: - Le network n'est pas utilisé - L'image n'était pas présente sur ma machine, `docker-compose` va pull l'image directement. - Le container est attaché au tty actuel et si on sort du container (`Ctrl-d` ou `exit`) le container est stoppé. ###### build La directive `build` va permettre à docker-compose de décrire à docker comment build son image, tous les champs de `docker build`. ```yml build: context: ./dir dockerfile: Dockerfile-alternate args: buildno: 1 ``` On peut donc spécifier: - Le dossier contexte dans lequel faire le build - Le nom du ficher `Dockerfile` - Les ARGS a donner au fichier Essayons: - Si on reprend le `dockerfile` pour le hello world en rust (ne pas oublier le fichier `app.rs`): ```dockerfile FROM rust:1.31 COPY app.rs . RUN rustc app.rs -o app ARG LANG RUN ./app ``` - Et on peut faire un fichier `docker-compose.yml` pour build: ```yml version: '3.6' services: my_rusty_docker: image: tommoulard/my_rusty-container build: context: . dockerfile: Dockerfile args: LANG: EN ``` - Et il n'y a plus qu'à lancer: ```bash > docker-compose build Building my_rusty_docker Step 1/5 : FROM rust:1.31 1.31: Pulling from library/rust cd8eada9c7bb: Pull complete c2677faec825: Pull complete fcce419a96b1: Pull complete 045b51e26e75: Pull complete 3b969ad6f147: Pull complete 2074c6bfed7d: Pull complete Digest: sha256:e2c4e3751290e30c3f130ef3513c7999aee87b5e7ac91e2fc9f3addcdf1f1387 Status: Downloaded newer image for rust:1.31 ---> 6f61eb35ad91 Step 2/5 : COPY app.rs . ---> 938d9a7ff71d Step 3/5 : RUN rustc app.rs -o app ---> Running in f25b678abb66 Removing intermediate container f25b678abb66 ---> 9e6749471396 Step 4/5 : ARG LANG=FR ---> Running in 1d1cb6dd7206 Removing intermediate container 1d1cb6dd7206 ---> 3434f48a83da Step 5/5 : RUN ./app ---> Running in 1a17201ae1d1 Hello World Removing intermediate container 1a17201ae1d1 ---> 61a07c21cc05 Successfully built 61a07c21cc05 Successfully tagged tommoulard/my_rusty-container:latest ``` ###### restart Cette option permet de définir la politique de redémarrage du container en cas d'arrêt (segfault, erreur, ...). Elle est définit comme: ```yml restart: "no" # do not restart (default value) restart: always # alway restart restart: on-failure # restart only when there's an error (exit code != 0) restart: unless-stopped # always restart the container except when the container is stopped (manually or otherwise) ``` Tips: utiliser `restart: unless-stopped` est souvent une bonne idée. Par exemple: - On prend un fichier `docker-compose.yml` vierge, et on y met: ```yml version: '3.6' services: ubuntrash: image: ubuntu:20.04 command: ["/bin/sh", "-c", "echo 'failure'", "&&", "exit 1"] restart: always ``` - Si on build l'image, on obtient: ```bash > docker-compose up Creating network "demo_default" with the default driver Pulling ubuntrash (ubuntu:20.04)... 20.04: Pulling from library/ubuntu 6a5697faee43: Pull complete ba13d3bc422b: Pull complete a254829d9e55: Pull complete Digest: sha256:fff16eea1a8ae92867721d90c59a75652ea66d29c05294e6e2f898704bdb8cf1 Status: Downloaded newer image for ubuntu:20.04 Creating demo_ubuntrash_1 ... done Attaching to demo_ubuntrash_1 ubuntrash_1 | failure demo_ubuntrash_1 exited with code 0 demo_ubuntrash_1 exited with code 0 ``` - Si on inspecte le container, on peut voir qu'il restart sans cesse: ```bash > docker-compose ps Name Command State Ports -------------------------------------------------------------------- demo_ubuntrash_1 echo failure Restarting > docker-compose logs ubuntrash Attaching to demo_ubuntrash_1 ubuntrash_1 | failure ubuntrash_1 | failure ubuntrash_1 | failure ubuntrash_1 | failure ubuntrash_1 | failure ubuntrash_1 | failure ubuntrash_1 | failure ubuntrash_1 | failure ubuntrash_1 | failure ubuntrash_1 | failure ubuntrash_1 | failure ``` Tips: Si vous voulez arrêter le redémarrage en boucle des services, vous pouvez faire `docker-compose down` ###### network Pour faire un lien réseau entre plusieurs container, on peut utiliser `networks`. Si on veut connecter une base de données et un backend d'application, on peut les mettre sur le même network et ainsi pouvoir scale la DB autant que possible: ```mermaid graph LR subgraph application-network r[redis] lb[Load Balancer] lb --> b lb --> r subgraph db-network b[backend] db1[(Database)] db2[(Database)] db3[(Database)] b --> db1 b --> db2 b --> db3 end end ``` Et comme docker c'est super cool, depuis l'intérieur d'un container, pour accéder aux autres containers, leur hostname corresponds à leurs noms (docker is love). Si on fait `docker-compose up` avec ce fichier: ```yml version: '3.6' networks: demo-net: services: python: image: python:3.9-alpine command: ["/bin/ping", "-c", "5", "my-nginx"] networks: - 'demo-net' my-nginx: image: nginx:alpine networks: - 'demo-net' ``` On obtient: ```bash > docker-compose up Recreating demo_python_1 ... done Starting demo_my-nginx_1 ... done Attaching to demo_my-nginx_1, demo_python_1 python_1 | PING my-nginx (172.23.0.2): 56 data bytes python_1 | 64 bytes from 172.23.0.2: seq=0 ttl=64 time=0.095 ms my-nginx_1 | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration my-nginx_1 | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/ my-nginx_1 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh my-nginx_1 | 10-listen-on-ipv6-by-default.sh: error: IPv6 listen already enabled my-nginx_1 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh my-nginx_1 | /docker-entrypoint.sh: Configuration complete; ready for start up python_1 | 64 bytes from 172.23.0.2: seq=1 ttl=64 time=0.151 ms python_1 | 64 bytes from 172.23.0.2: seq=2 ttl=64 time=0.149 ms python_1 | 64 bytes from 172.23.0.2: seq=3 ttl=64 time=0.141 ms python_1 | 64 bytes from 172.23.0.2: seq=4 ttl=64 time=0.151 ms python_1 | python_1 | --- my-nginx ping statistics --- python_1 | 5 packets transmitted, 5 packets received, 0% packet loss python_1 | round-trip min/avg/max = 0.095/0.137/0.151 ms demo_python_1 exited with code 0 ^CGracefully stopping... (press Ctrl+C again to force) Stopping demo_my-nginx_1 ... done ``` Tips, pour faire des connections en "One to many", on peut utiliser `links` ###### environment Une best practice de docker-compose est de donner la configuration des process qui tournent dans un container par des variables d'environnement. On peut utiliser les variables d'environnement de deux manières différentes: ```yml environment: MYSQL_ROOT_PASSWORD: example # or - 'MYSQL_ROOT_PASSWORD=example' ``` Par exemple, le hub de [mysql](https://hub.docker.com/_/mysql), nous propose de faire: ```yml version: '3.1' services: db: image: mysql command: --default-authentication-plugin=mysql_native_password restart: always environment: MYSQL_ROOT_PASSWORD: example adminer: image: adminer restart: always ports: - 8080:8080 ``` Et après avoir fait `docker-compose up` et être allé sur [localhost:8080](http://localhost:8080). On peut voir qu'`adminer` nous permet de nous connecter avec les credentials `root:example` sur mysql et donc que le mdp par défaut de la base de donnée a été changé. ###### volumes Comme nous avons vu précédemment, on aimerait bien garder des données sur le disk même si on supprime le container, et pour ça on utilise des volumes. Les volumes virtuels sont bien mais il y a toujours des frictions pour les utiliser. On a donc deux manières d'utiliser des volumes: ```yml volumes: - 'data:/data' # With docker virtual fs - '/srv/data:/data' # Using a file or a folder using the absolute path - './data:/data' # docker-compose allows to use a relative path too ``` On les reconnait grâce au premier caractère: - si on a un `/` ou un `.`, c'est un fs local - sinon, c'est un file system virtuel Pour reprendre l'exemple de mysql, si on a envi d'utiliser des volumes, on peut faire: ```yml version: '3.1' services: db: image: mysql command: --default-authentication-plugin=mysql_native_password restart: always environment: MYSQL_ROOT_PASSWORD: example volumes: - './mysql/data:/var/lib/mysql' adminer: image: adminer restart: always ports: - 8080:8080 ``` Tips, si on utilise un chemin du fs courant, et que le dossier n'existe pas, docker va en créer un par défaut. ###### ports Vous l'avez sûrement deviné avec l'exemple précédent, l'argument `port` permet d'exporter des ports. Ils ont deux modes d'expressions: ```yml ports: # shorts - "3000" - "3000-3005" - "8000:8000" - "9090-9091:8080-8081" - "49100:22" - "127.0.0.1:8001:8001" - "127.0.0.1:5000-5010:5000-5010" - "6060:6060/udp" - "12400-12500:1240" # long - target: 80 published: 8080 protocol: tcp mode: host ``` ###### depends_on `depends_on` permet de dire à docker-compose d'attendre que le container démarre avant de lancer le suivant. On pourrait avoir envie d'attendre que la base de données soit initialisée avant de lancer le container qui va l'utiliser. Attention, `depends_on` n'attend pas l'initialisation complète du container, il attend qu'il soit lancé. Si vous voulez attendre l'initialisation, je vous conseille d'aller jeter un coup d'œil [ici](https://docs.docker.com/compose/startup-order/). Par exemple, pour déployer un site en django, on pourrait avoir le fichier `docker-compose.yml`: ```yml version: '3.6' services: db: image: postgres django: build: . command: python manage.py runserver 0.0.0.0:8000 volumes: - '.:/code' ports: - '8000:8000' depends_on: - 'db' ``` ##### Fichiers `.env` `docker-compose` va lire automatiquement les fichiers `.env` que vous aurez dans vos dossier pour avoir des configurations plus explicites. ##### Aller plus loin - secrets - labels - user #### CLI - `ps` - `up` / `down` - `build` ##### Aller plus loin - `scale` - Ne pas avoir tous ses services dans un seul fichier `docker-compose.yml` # Projet Pour s'amuser un peu après autant de cours, on va faire une petite mise en pratique. Le but va être de compiler un projet en Go et de le déployer pour l'utiliser. Pour cela, vous trouverez sur le [repository github](https://github.com/tomMoulard/gochain) tous les fichiers nécessaires pour faire le devoir. ![Fork](https://github-images.s3.amazonaws.com/help/bootcamp/Bootcamp-Fork.png) Le rendu du devoir se fera sur github directement. Pour cela, il va falloir fork le [projet](https://github.com/tomMoulard/gochain) et réaliser le maximum d'étapes. Nous regarderons les forks du projets à la fin du temps impartit (cf le haut du TP). N'oubliez pas de push ce que vous faites ! Toutes les optimisations seront regardées et prises en compte, donc n'hésitez pas à en faire ! ## Docker La première étape va être de créer une image avec le binaire go. Points bonus: Celui qui fait l'image la plus petite gagne ! ### Compiler du go Pour compiler un binaire go, il suffit de faire: ```bash go get -d -v \ # to get dependencies github.com/lib/pq \ github.com/julienschmidt/httprouter go build -o a.out # to compile to a binary a.out ``` Tips, On peut compiler du go statiquement (avec toutes les librairies) avec quelques variations: ```bash CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o a.out ``` Ne pas oublier d'exposer un port (je conseille d'utiliser le port 8000). ## docker-compose Après avoir créé l'image Docker, il faut l'utiliser. On va donc devoir lancer le container, exposer les ports et configurer l'app. L'application Go utilise 4 arguments (via les variables d'environnement): - `IP`: L'IP à utiliser pour l'application - `PORT`: Le port que va bind l'application - `POSTGRES_URL`: L'adresse de la base de données - `POSTGRES_PASSWORD`: Le mot de passe de la base de données ### Postgresql Il faudra aussi, pour que l'application fonctionne, une base de données : `postgres`. Il faudra donc que l'application go soit capable d'accéder à la base de données. Et il faudra faire en sorte que les données (situées dans `/var/lib/postgresql/data`) soient préservées. Tips, il n'y a pas besoin de toucher aux user de Postgres, il faut set le password. ## Test Pour tester, il n'y a rien de mieux que de faire `docker build` puis `docker-compose up`, n'hésitez pas à le faire régulièrement. Il ne faut surtout pas hésiter à poser des questions aux assistants, quitte à se faire debugger. Ils sont là pour ça. Ne les laissez pas s'ennuyer. Pour aller voir l'interface web, il faut aller sur [localhost:8000](http://localhost:8000). Pour ajouter des block dans la chaine, il faut faire: ```bash= curl 0.0.0.0:8000/add -d '{"data":"Mon block, Vive les TCOMs"}' ``` ## Bonus Si vous avez tout fait, vous trouverez un fichier yaml `openapi.yml` qui est une spécification de type OpenAPI qui fonctionne avec [swagger](http://swagger.io/). Vous pouvez tenter de lancer le service en même temps que le reste de l'application. # Clean des images Si vous voulez clean tout ce qui a été fait sur votre machine depuis le début du TP, vous pouvez: - `docker system prune`: cela permet de supprimer toutes les données de docker qui ne sont plus utilisées. - Désinstaller docker. # Aller plus loin - [jessfraz](https://blog.jessfraz.com/): [contenerize everything](https://www.youtube.com/watch?v=cYsVvV1aVss) - [Analyse statique de vulnérabilités pour des containers](https://github.com/quay/clair), [HN: security best practices](https://cloudberry.engineering/article/dockerfile-security-best-practices/) - [Avoir son serveur maison](https://github.com/tommoulard/make-my-server) - Podman - BSD Jail - chroot - Linux-VServer - LXC - OpenVZ - [hadolint: Dockerfile linter](https://github.com/hadolint/hadolint) - [Faire des images multi architectures](https://blog.moulard.org/multi-arch-golang-app-docker/) - One of many [docker cheat sheet](https://gwendal.orinel.net/2019/06/27/docker_tricks/) - k8s, k3s, micro k8s - `docker run -it traefik/jobs` ![HitCount](https://hits.cyprin.eu/assistants/tcom/docker.svg)

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully