Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
Beide Seiten der vorigen Revision Vorhergehende Überarbeitung Nächste Überarbeitung | Vorhergehende Überarbeitung Nächste ÜberarbeitungBeide Seiten der Revision | ||
server:docker [2018/07/12 17:50] – [docker Apparmor] st | server:docker [2023/04/25 17:25] – [Routingkonflikte mit docker networks] st | ||
---|---|---|---|
Zeile 1: | Zeile 1: | ||
+ | ====== Docker ====== | ||
+ | |||
+ | [[wpde> | ||
+ | |||
+ | Docker vereinfacht die Bereitstellung von Anwendungen, | ||
+ | |||
+ | [[wpde> | ||
+ | |||
+ | ===== Docker Einsatz ===== | ||
+ | |||
+ | **Vorteile**: | ||
+ | * Docker zieht eine Abstraktionsebene ein, d.h. die darunterliegende Struktur (LAN, Storage, Distribution und Paketabhängigkeiten) muss bei der Anwendung nicht betrachtet werden. Überall lauffähig, nur die docker-version muss beachtet werden | ||
+ | * Ressourceneinsatz (Massenhosting) | ||
+ | * Skalierbarkeit (Lastspitzen abfangen) | ||
+ | * Deployment kann schnell und oft erfolgen | ||
+ | |||
+ | |||
+ | **Nachteile**: | ||
+ | * in Hochsicherheitsumgebungen nicht einsetzbar | ||
+ | * nicht zertifizierbar, | ||
+ | * zur Abschottung wird eine VM außendrum benötigt (oder halt [[kubernetes]] mit seinen pods) | ||
+ | * Angriffsvektoren durch [[https:// | ||
+ | * extern gebaute Images sind ein Risiko, Einzelfallprüfung vom wem ein image gebaut wird (Signierung wäre nötig...) | ||
+ | * [[https:// | ||
+ | * monolitische Software (mehrere Dienste in einem Container ...) lassen Vorteile ungenutzt | ||
+ | * Anwendungen die stateful sind (Datenbanken, | ||
+ | * umfangreiche Umgebungsabhängigkeiten (Repository, | ||
+ | * debugging wird komplexer: | ||
+ | * Zusammenspiel der microservices | ||
+ | * networking | ||
+ | * in Container hinein gehen, teilweise stehen dort debugging tools nicht zur Verfügung | ||
+ | |||
+ | |||
+ | ==== Welche Software ist geeignet? ==== | ||
+ | |||
+ | geeignet: | ||
+ | - eigenprogrammierte Software mit CI/ | ||
+ | - microservice-Architektur | ||
+ | - stateless services | ||
+ | |||
+ | |||
+ | eher nicht | ||
+ | - Drittanbietersoftware ohne microservices | ||
+ | - stateful-software wo Skalierung unerwünscht oder kontraproduktiv ist | ||
+ | |||
+ | |||
+ | ==== Was sind Probleme die gelöst werden müssen? ==== | ||
+ | |||
+ | - config via environment... boolean parsing ... keine komplexen configs (nur key-value) -> mount-points mit komplexer Config -> Clusterdateisystem? | ||
+ | - cron (logging) | ||
+ | - container bestehen nicht nur aus der app sondern auch die Laufzeit-umgebung: | ||
+ | - Alpine veröffentlich nur Daten zu bereits zu gefixten Problemen! Updategeschwindigkeit eher schlecht | ||
+ | |||
+ | siehe auch [[https:// | ||
+ | ===== Einrichtung ===== | ||
+ | |||
+ | Bevorzugte Methode ist die Einbindung via repo, fast immer ist die bei Distributionen mitgelieferte Version zu alt. | ||
+ | |||
+ | [[https:// | ||
+ | |||
+ | ==== docker-compose ==== | ||
+ | https:// | ||
+ | |||
+ | ==== docker ohne root ausführen ==== | ||
+ | |||
+ | Aktuellen Benutzer zur Gruppe docker hinzufügen | ||
+ | |||
+ | sudo usermod -aG docker $USER | ||
+ | |||
+ | -> ausloggen und einloggen nötig | ||
+ | |||
+ | |||
+ | ==== Kernel Kompatibilität ==== | ||
+ | |||
+ | Docker braucht mindestens die Kernel Version 3.10. | ||
+ | Ob die richtigen Konfiguration aktiviert ist, lässt sich mit dem Skript check-config.sh überprüfen: | ||
+ | |||
+ | <code bash> | ||
+ | curl https:// | ||
+ | bash ./ | ||
+ | </ | ||
+ | |||
+ | bei [[ubuntu: | ||
+ | |||
+ | ''/ | ||
+ | GRUB_CMDLINE_LINUX_DEFAULT=" | ||
+ | |||
+ | <code bash> | ||
+ | |||
+ | Optional: | ||
+ | * '' | ||
+ | * '' | ||
+ | |||
+ | |||
+ | === Testen ob cgroup-memory limit wirkt === | ||
+ | |||
+ | <code bash> | ||
+ | |||
+ | '' | ||
+ | |||
+ | ...mit tatsächlichem Prozess (Quelle https:// | ||
+ | |||
+ | <code bash> | ||
+ | sudo docker -m 512M -it ubuntu /bin/bash | ||
+ | sudo apt-get update && apt-get install -y build-essential vim | ||
+ | </ | ||
+ | |||
+ | -> vim foo.c: | ||
+ | |||
+ | < | ||
+ | #include < | ||
+ | #include < | ||
+ | |||
+ | int main(void) { | ||
+ | int i; | ||
+ | for (i=0; i<65536; i++) { | ||
+ | char *q = malloc(65536); | ||
+ | printf (" | ||
+ | } | ||
+ | sleep(9999999); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Compile the file | ||
+ | |||
+ | <code bash> | ||
+ | gcc -o foo foo.c | ||
+ | ./foo | ||
+ | </ | ||
+ | |||
+ | ==== lokale Netze von Docker schützen (swarm) ==== | ||
+ | |||
+ | Beispiel: lokal nur noch das Netz 10.254.0.1/ | ||
+ | <code bash> | ||
+ | (als root) echo ' | ||
+ | sudo systemctl restart docker.service | ||
+ | </ | ||
+ | |||
+ | * ggf. Netzwerke aufräumen: <code bash> | ||
+ | * routen checken | ||
+ | * docker0 bridge da? <code bash> | ||
+ | |||
+ | |||
+ | ==== Routingkonflikte mit docker networks (IPv4) ==== | ||
+ | |||
+ | Netze die sich mit Docker überlappen (172.17., ...) sind ein Problem. | ||
+ | Die Config " | ||
+ | |||
+ | Bei docker standalone wird diese Einstellung beim Anlegen weiterer docker networks ignoriert. Auch andere Einstellungen wie // | ||
+ | |||
+ | Es gibt nur einen funktionierenden workaround: explizit eine route im System setzen: | ||
+ | |||
+ | Beispiel Redhat (ens192 mit Standard-GW 172.17.0.1) z.B. mit ''/ | ||
+ | | ||
+ | Bei Swarm muss initial die docker_gwbridge | ||
+ | |||
+ | - löschen der vorhandene bridge (falls noch vorhanden): <code bash> | ||
+ | - neu anlegen mit anderem Netz (muss vor join erfolgen!):< | ||
+ | docker network create \ | ||
+ | --subnet 10.4.0.0/16 \ | ||
+ | --opt com.docker.network.bridge.name=docker_gwbridge \ | ||
+ | --opt com.docker.network.bridge.enable_icc=false \ | ||
+ | --opt com.docker.network.bridge.enable_ip_masquerade=true \ | ||
+ | docker_gwbridge | ||
+ | </ | ||
+ | |||
+ | https:// | ||
+ | |||
+ | |||
+ | ==== docker Apparmor ==== | ||
+ | |||
+ | docker wendet für Gäste das Profil docker-default an. | ||
+ | |||
+ | Dazu wird beim Starten eines Containers ein [[https:// | ||
+ | |||
+ | Oder es wird das Profil benutzt, das via security-opt angegeben wurde: <code bash> | ||
+ | |||
+ | Für den docker daemon selbst wird aktuell kein apparmor-Profil geladen. | ||
+ | |||
+ | Doku: [[https:// | ||
+ | |||
+ | |||
+ | ===== Benutzung ===== | ||
+ | |||
+ | * Version anzeigen: <code bash> | ||
+ | |||
+ | ==== Container Laufzeitbefehle ==== | ||
+ | |||
+ | |||
+ | Container: | ||
+ | * start: docker start $ID | ||
+ | * stop Container: docker stop $ID | ||
+ | * restart: docker restart $ID | ||
+ | * Befehle in einem Container mit $ID bash ausführen: <code bash> | ||
+ | * attach: docker attach $ID | ||
+ | * detach (funktioniert nur wenn -ti angegeben wurde!): STRG-P STRG-Q ((Tastenkombination ändern mit: https:// | ||
+ | |||
+ | |||
+ | === Container via netzwerk anbinden === | ||
+ | |||
+ | Container exposen via Netzwerk: <code bash> | ||
+ | |||
+ | Beispiel (offizielles redis-image): | ||
+ | |||
+ | |||
+ | **Netzwerke** | ||
+ | * anzeigen: <code bash> | ||
+ | * anlegen: <code bash> | ||
+ | * informationen:< | ||
+ | * container in das Netzwerk integrieren: | ||
+ | |||
+ | |||
+ | == Container via Firewall absichern == | ||
+ | |||
+ | Wenn Container nach Außen exposed werden, legt Docker entsprechende [[linux: | ||
+ | |||
+ | Docker ([[https:// | ||
+ | |||
+ | Beispiel für Port 9200 (Elasticsearch): | ||
+ | <code bash> | ||
+ | iptables -I DOCKER-USER 1 -i eth0 -s 91.213.91.0/ | ||
+ | iptables -I DOCKER-USER 2 -i eth0 -p TCP --dport 9200 -j LOG --log-prefix " | ||
+ | iptables -I DOCKER-USER 3 -i eth0 -p TCP --dport 9200 -j REJECT | ||
+ | </ | ||
+ | |||
+ | Löschen wäre (hier Regel nr.1): | ||
+ | |||
+ | iptables -D DOCKER-USER 1 usw. | ||
+ | |||
+ | Auflistung aktuell gültiger Regeln: <code bash> | ||
+ | |||
+ | |||
+ | === Diagnose / | ||
+ | |||
+ | |||
+ | |||
+ | Container auflisten: | ||
+ | * laufende: docker container ls | ||
+ | * alle: docker container ls --all | ||
+ | |||
+ | * ID anzeigen: <code bash> | ||
+ | * ID des zuletzt gestarteten containers anzeigen: <code bash> | ||
+ | * ausführliche Informationen anzeigen: docker inspect $ID | ||
+ | * Ressourcenverbrauch laufende Container: docker stats | ||
+ | * Prozesse eines Containers anzeigen: docker top $ID | ||
+ | |||
+ | |||
+ | == Logging == | ||
+ | |||
+ | |||
+ | [[https:// | ||
+ | |||
+ | <code bash> | ||
+ | |||
+ | " | ||
+ | |||
+ | == Image startet nicht == | ||
+ | |||
+ | überschreibt den entrypoint aus dem image, einloggen mit bash ist möglich: | ||
+ | |||
+ | '' | ||
+ | < | ||
+ | entrypoint: | ||
+ | - "/ | ||
+ | - " | ||
+ | - "/ | ||
+ | </ | ||
+ | |||
+ | |||
+ | |||
+ | === Compose-file via systemd starten === | ||
+ | |||
+ | |||
+ | compose-File liegt hier: / | ||
+ | unitfile: ''/ | ||
+ | |||
+ | < | ||
+ | [Unit] | ||
+ | Description=meinDockerService | ||
+ | Requires=docker.service | ||
+ | After=docker.service | ||
+ | |||
+ | [Service] | ||
+ | Restart=always | ||
+ | #User=root | ||
+ | # | ||
+ | # add group to user: usermod -a -G docker < | ||
+ | # Shutdown container (if running) when unit is stopped | ||
+ | ExecStartPre=/ | ||
+ | # Start container when unit is started | ||
+ | ExecStart=/ | ||
+ | # Stop container when unit is stopped | ||
+ | ExecStop=/ | ||
+ | [Install] | ||
+ | WantedBy=multi-user.target | ||
+ | </ | ||
+ | |||
+ | Dienst laden, starten und autostart aktivieren: | ||
+ | <code bash> | ||
+ | systemctl daemon-reload | ||
+ | systemctl start docker-datadog.service | ||
+ | systemctl enable docker-datadog.service | ||
+ | </ | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | ==== Image-handling ==== | ||
+ | |||
+ | |||
+ | Docker images anzeigen: <code bash> | ||
+ | |||
+ | === image erstellen === | ||
+ | |||
+ | Dockerfile <-> Docker Compose file | ||
+ | image bauen <-> image deployen | ||
+ | |||
+ | |||
+ | Beispiele: | ||
+ | |||
+ | - Image " | ||
+ | - <code bash> | ||
+ | |||
+ | - <code bash> | ||
+ | |||
+ | |||
+ | [[https:// | ||
+ | |||
+ | **image aus docker-compose-file erstellen**: | ||
+ | |||
+ | **image aus Dockerfile erstellen**: | ||
+ | - Dockerfile anlegen und ins Verzeichnis wechseln (tag latest wird angehängt)< | ||
+ | FROM base | ||
+ | RUN apt-get install hello | ||
+ | CMD hello | ||
+ | </ | ||
+ | - oder Pfad zu meinDockerfile angeben: | ||
+ | - <code bash> | ||
+ | |||
+ | **image aus laufendem container erstellen** | ||
+ | * <code bash> | ||
+ | * <code bash> | ||
+ | |||
+ | === best practises image-bau === | ||
+ | |||
+ | - allgemein bei docker images: Prozess sollte als PID 1 laufen damit es die Signale bekommt (SIGTERM, ...) | ||
+ | - möglichst wenige RUNs: (neue files, neue layers) | ||
+ | - [[https:// | ||
+ | - NOTE: If you are using Docker 1.13 or greater, Tini is included in Docker itself. This includes all versions of Docker CE. To enable Tini, just pass the --init flag to docker run. | ||
+ | - ReadMe.md anlegen im git-Repo | ||
+ | |||
+ | |||
+ | Schlecht: | ||
+ | - config-file ins image und dann kein vollwertiges base-linux drinhaben (nur per export auslesbar) | ||
+ | - config-file extern als volume einhängen (liegt auf host rum...) | ||
+ | |||
+ | gut: | ||
+ | - config als extra layer einbauen das das install-Image aus einer registry benutzt und dann schnell neu gebaut werden kann | ||
+ | - jedes image kommt aus einem eigenen repository (erleichert automatisierten image neubau, was wurde wann zuletzt benutzt, gebaut?) | ||
+ | |||
+ | |||
+ | === Registries benutzen === | ||
+ | |||
+ | neben der öffentluchen Standard-registry von hub.docker.com können weitere registries verwendet werden. Insbesondere wenn die images privat sein sollen und um zu verhindern das durch externe Fehler Applikationen ihren Betrieb einstellen. | ||
+ | |||
+ | - offizielle registry (öffentlich): | ||
+ | - privates Repo auf docker hub mit Anmeldung: | ||
+ | - [[https:// | ||
+ | - mehr als ein privates Repo [[https:// | ||
+ | - | ||
+ | - [[https:// | ||
+ | - [[https:// | ||
+ | - [[https:// | ||
+ | - weitere Software | ||
+ | - z.B. gitlab container registry (ab v.8.8): https:// | ||
+ | |||
+ | |||
+ | == unsichere registry === | ||
+ | |||
+ | [[https:// | ||
+ | |||
+ | Datei / | ||
+ | < | ||
+ | |||
+ | Restart Docker: <code bash> | ||
+ | |||
+ | |||
+ | === image in registry committen === | ||
+ | |||
+ | * <code bash> | ||
+ | * image auf **private** registry hochladen: | ||
+ | * Login: <code bash> | ||
+ | * push: <code bash> | ||
+ | |||
+ | |||
+ | === image löschen === | ||
+ | |||
+ | <code bash> | ||
+ | |||
+ | === Image exportieren === | ||
+ | |||
+ | **Methode 1 (docker save)**: | ||
+ | <code bash> | ||
+ | |||
+ | $IMAGE z.B. hello/ | ||
+ | |||
+ | **Methode 2 (docker export)** (laufendes image exportieren): | ||
+ | * in tar-Archiv: <code bash> | ||
+ | * in tar.gzip-Archiv: | ||
+ | |||
+ | === Image importieren === | ||
+ | |||
+ | aus tar.gz-Archiv: | ||
+ | |||
+ | |||
+ | === Beispiele für images === | ||
+ | |||
+ | * Apache allein: https:// | ||
+ | * PHP mit Apache: https:// | ||
+ | * <code bash> | ||
+ | * <code bash> | ||
+ | * Nginx: https:// | ||
+ | * wordpress: https:// | ||
+ | * <code bash> | ||
+ | * <code bash> | ||
+ | * ceph: https:// | ||
+ | |||
+ | |||
+ | ==== Volume-handling ==== | ||
+ | |||
+ | Im Gegensatz zu den flüchtigen Containern sind volumes geeignet permanente Daten aufzunehmen (" | ||
+ | |||
+ | -v / | ||
+ | |||
+ | |||
+ | * **erzeugen**: | ||
+ | * **mounten**: | ||
+ | * **auflisten**: | ||
+ | * **details**: | ||
+ | * **unbenutzte volumes anzeigen**: <code bash> | ||
+ | * **unbenutzte volumes löschen** ((oder bzw. bei älteren Versionen:< | ||
+ | * **bestimmtes Volume löschen**:< | ||
+ | * [[https:// | ||
+ | |||
+ | |||
+ | |||
+ | ===== docker swarm ===== | ||
+ | |||
+ | * docker swarm join-token manager | ||
+ | * docker swarm join-token worker | ||
+ | * Node entfernen: | ||
+ | * docker swarm leave | ||
+ | * auf manager node: docker node rm $Name -f | ||
+ | * manager müssen zuerst heruntergestuft werden: docker node demote $Name | ||
+ | * Nodes | ||
+ | * anzeigen: docker node ls | ||
+ | * detaillierter: | ||
+ | * laufende Dienste auf §node: docker node ps $Node | ||
+ | * Debugging | ||
+ | * container-ID finden: docker service ps | ||
+ | * docker exec -it $ContainerID / | ||
+ | * als root: docker exec -ti -u root $ContainerID bash | ||
+ | |||
+ | |||
+ | docker secret ls | ||
+ | |||
+ | mkfs.xfs -f -n ftype=1 $disk | ||
+ | |||
+ | |||
+ | * Infos über alle laufenden dienste auf: docker service ls | ||
+ | * Übericht der stacks: docker stack ls | ||
+ | * Übersicht über einzelne Services eines Stacks: docker stack ps $stackname | ||
+ | * Ausführliche Infos über einen gestarteten Service (geht auch für Container, Storage, Networks...): | ||
+ | * Logoutput einzelner Dienste ansehen: docker service logs $name | ||
+ | |||
+ | |||
+ | docker vsphere plugin((Achtung: | ||
+ | |||
+ | ==== Deployments in stacks ==== | ||
+ | |||
+ | * Deployment: <code bash> | ||
+ | * gucken ob der stack bereits fertig aktualisiert ist: <code bash> | ||
+ | |||
+ | * Rollback eines services: <code bash> | ||
+ | * löschen | ||
+ | * stack: <code bash> | ||
+ | * service: <code bash> | ||
+ | * update | ||
+ | * eines services: <code bash> | ||
+ | * < | ||
+ | * < | ||
+ | |||
+ | traeffik | ||
+ | * skalieren: <code bash> | ||
+ | * | ||
+ | ==== Wartung ==== | ||
+ | |||
+ | Leider nimmt docker keine automatischen Aufräumarbeiten vor. Deshalb muss manuell (ggf. via cronjobs) nachgeholfen werden: | ||
+ | |||
+ | ''/ | ||
+ | <code bash>0 4 * * * root docker rmi $(docker images -q) > / | ||
+ | |||
+ | etwas radikaler ist der folgende Befehl: | ||
+ | <code bash> | ||
+ | WARNING! This will remove: | ||
+ | - all stopped containers | ||
+ | - all networks not used by at least one container | ||
+ | - all images without at least one container associated to them | ||
+ | - all build cache | ||
+ | |||
+ | Are you sure you want to continue? [y/N] y | ||
+ | </ | ||
+ | |||
+ | |||
+ | |||
+ | ===== IPv6 ===== | ||
+ | |||
+ | Docker hat beim Design Ipv6 nicht berücksichtigt und daher schlagen dessen Simplifizierungen (Expose durch NAT nach Außen und interne DNS-Auflösung) in Nachteile um. | ||
+ | Ob NAT (analog zu v4) jemals offiziell mit IPv6 umgesetzt wird, ist unklar. Möglich wäre es, dazu würden aus dem Unique Local Addresses (ULA)-Bereich fd00::/8 Adressen ausgewählt werden und per firewall nach außen genattet werden müssen. | ||
+ | |||
+ | Alternativ kann intern ein öffentliches v6-Netz benutzt werden. Beispiel mit 2001: | ||
+ | / | ||
+ | <code bash> | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Weiterhin unterstützt docker **kein SLAAC oder DHCPv6** und kann daher nicht selbstständig Präfixe beziehen, d.h. es müssen statische IPs zugewiesen werden und diese via DNS propagiert werden (DNS auf die öffentliche IP des docker-Hosts reicht nicht mehr!). | ||
+ | Hiermit werden jedoch sämtliche Ports öffentlich zugänglich gemacht (nicht nur diese via EXPOSE) und müssen daher via firewall abgesichert werden. | ||
+ | |||
+ | Siehe auch: https:// | ||
+ | |||
+ | :!: Für IPv6-only ist dank der Rückständigkeit bei Dritten (Docker registry, Github etc.) Krücken wie DNS64 + NAT64 nötig. | ||
+ | |||
+ | |||
+ | ===== Clustering ===== | ||
+ | |||
+ | Zum orchestrieren von docker container steht das integrierte docker swarm zur Verfügung. | ||
+ | Mittlerweile hat sich [[Kubernetes]] für diese Aufgaben etabliert, da es deutlich mehr Funktionen (aber auch mehr Komplexität) mit sich bringt. | ||
+ | |||
+ | ==== docker swarm ==== | ||
+ | |||
+ | **Initialiseren**: | ||
+ | < | ||
+ | |||
+ | To add a worker to this swarm, run the following command: | ||
+ | |||
+ | <code bash> | ||
+ | |||
+ | To add a manager to this swarm, run ' | ||
+ | </ | ||
+ | In diesem Beispiel reicht zum **Beitritt** | ||
+ | - als node: <code bash> | ||
+ | - oder als **weiteren manager** die folgende Ausgabe auszuführen:< | ||
+ | |||
+ | Aus dem docker swarm **austreten**: | ||
+ | * <code bash> | ||
+ | * wenn manager: <code bash> | ||
+ | |||
+ | ***Informationen über sich selbst** anzeigen: <code bash> | ||
+ | |||
+ | |||
+ | === Nodes === | ||
+ | |||
+ | **Nodes** sind | ||
+ | - Active (neue tasks können zugewiesen werden) | ||
+ | - Pause (keine neuen, aber bestehende tasks bleiben) | ||
+ | - Drain (keine neuen, und existiere task werden heruntergefahren. <code bash> | ||
+ | |||
+ | [[https:// | ||
+ | * Nodes auflisten: <code bash> | ||
+ | |||
+ | === Manager === | ||
+ | |||
+ | **Manager** sind | ||
+ | - Reachable | ||
+ | - Unavailable | ||
+ | - Leader | ||
+ | |||
+ | |||
+ | <code bash> | ||
+ | < | ||
+ | content-sync | ||
+ | |||
+ | |||
+ | === Services === | ||
+ | |||
+ | * global: 1x pro Node | ||
+ | * replicated: x Instanzen | ||
+ | |||
+ | === Labels === | ||
+ | |||
+ | **Labels**: (wo fährt was hoch?), Beispiel: nur hochfahren wenn label db1 auf der node vorhanden ist: | ||
+ | < | ||
+ | | ||
+ | placement: | ||
+ | constraints: | ||
+ | - node.labels.db1 == TRUE | ||
+ | </ | ||
+ | - Label hinzufügen: | ||
+ | - Label updaten: <code bash> | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | ==== Zusatztools ==== | ||
+ | |||
+ | === docker-machine === | ||
+ | |||
+ | [[https:// | ||