# Kubernetesの基本 ## Kubernetesの基本 {{#include ../../banners/hacktricks-training.md}} **このページの元の著者は** [**Jorge**](https://www.linkedin.com/in/jorge-belmonte-a924b616b/) **(彼の元の投稿を** [**こちら**](https://sickrov.github.io)**で読む)** ## アーキテクチャと基本 ### Kubernetesは何をするのか? - コンテナエンジンでコンテナを実行できる。 - スケジュールによりコンテナのミッションを効率的に行う。 - コンテナを生存させる。 - コンテナ間の通信を可能にする。 - デプロイメント技術を許可する。 - 情報のボリュームを処理する。 ### アーキテクチャ ![](https://sickrov.github.io/media/Screenshot-68.jpg) - **ノード**: ポッドまたはポッドを持つオペレーティングシステム。 - **ポッド**: コンテナまたは複数のコンテナを包むラッパー。ポッドは通常、1つのアプリケーションのみを含むべきである(通常、ポッドは1つのコンテナを実行する)。ポッドはKubernetesが実行しているコンテナ技術を抽象化する方法である。 - **サービス**: 各ポッドはノードの内部範囲から1つの内部**IPアドレス**を持つ。しかし、サービスを介しても公開されることがある。**サービスにもIPアドレスがあり**、その目的はポッド間の通信を維持することである。したがって、1つのポッドが死んだ場合、**新しい置き換え**(異なる内部IPを持つ)**がサービスの同じIPでアクセス可能になる**。内部または外部として構成できる。サービスはまた、**2つのポッドが同じサービスに接続されているときにロードバランサーとして機能する**。\ **サービス**が**作成**されると、`kubectl get endpoints`を実行して各サービスのエンドポイントを見つけることができる。 - **Kubelet**: プライマリノードエージェント。ノードとkubectl間の通信を確立するコンポーネントであり、ポッドのみを実行できる(APIサーバーを介して)。KubeletはKubernetesによって作成されていないコンテナを管理しない。 - **Kube-proxy**: apiserverとノード間の通信(サービス)を担当するサービス。ノードのためのIPtablesが基本である。経験豊富なユーザーは、他のベンダーからの他のkube-proxiesをインストールすることができる。 - **サイドカーコンテナ**: サイドカーコンテナは、ポッド内のメインコンテナと一緒に実行されるべきコンテナである。このサイドカーパターンは、現在のコンテナの機能を変更することなく拡張し強化する。現在、私たちはアプリケーションがどこでも実行できるようにすべての依存関係をラップするためにコンテナ技術を使用していることを知っている。コンテナは1つのことだけを行い、そのことを非常にうまく行う。 - **マスタープロセス:** - **Api Server:** ユーザーとポッドがマスタープロセスと通信するための方法である。認証されたリクエストのみが許可されるべきである。 - **スケジューラ**: スケジューリングは、ポッドがノードにマッチすることを確認することを指す。Kubeletがそれらを実行できるようにする。どのノードがより多くのリソースを利用可能かを決定するための十分な知性を持ち、新しいポッドをそれに割り当てる。スケジューラは新しいポッドを開始するのではなく、ノード内で実行されているKubeletプロセスと通信し、新しいポッドを起動する。 - **Kube Controller manager**: レプリカセットやデプロイメントなどのリソースをチェックし、例えば、正しい数のポッドやノードが実行されているかを確認する。ポッドが欠けている場合、新しいポッドを開始するためにスケジューラと通信する。APIへのレプリケーション、トークン、およびアカウントサービスを制御する。 - **etcd**: データストレージ、永続的、一貫性があり、分散型。Kubernetesのデータベースであり、クラスターの完全な状態を保持するキー-バリューストレージ(各変更はここにログされる)。スケジューラやコントローラーマネージャーなどのコンポーネントは、どの変更が発生したかを知るためにこのデータに依存する(ノードの利用可能なリソース、実行中のポッドの数...)。 - **Cloud controller manager**: フロー制御とアプリケーションのための特定のコントローラーである。つまり、AWSやOpenStackにクラスターがある場合。 ノードが複数(複数のポッドを実行)される可能性があるため、Apiサーバーへのアクセスが負荷分散され、etcdが同期される複数のマスタープロセスも存在する可能性がある。 **ボリューム:** ポッドがデータを作成し、そのポッドが消えるときに失われるべきでない場合、それは物理ボリュームに保存されるべきである。**Kubernetesはデータを永続化するためにポッドにボリュームをアタッチすることを許可する**。ボリュームはローカルマシンまたは**リモートストレージ**に存在する可能性がある。異なる物理ノードでポッドを実行している場合、すべてのポッドがアクセスできるようにリモートストレージを使用するべきである。 **その他の構成:** - **ConfigMap**: サービスにアクセスするための**URL**を構成できる。ポッドはここからデータを取得して、他のサービス(ポッド)と通信する方法を知る。これは資格情報を保存するための推奨場所ではないことに注意! - **Secret**: パスワード、APIキーなどの**秘密データ**をB64でエンコードして保存する場所である。ポッドは必要な資格情報を使用するためにこのデータにアクセスできる。 - **Deployments**: これはKubernetesによって実行されるコンポーネントが示される場所である。ユーザーは通常ポッドと直接作業しない。ポッドは**ReplicaSets**(複製された同じポッドの数)で抽象化され、デプロイメントを介して実行される。デプロイメントは**ステートレス**アプリケーションのためのものであることに注意。デプロイメントの最小構成は、名前と実行するイメージである。 - **StatefulSet**: このコンポーネントは、**データベース**のようなアプリケーションのために特に設計されており、**同じストレージにアクセスする**必要がある。 - **Ingress**: これは**アプリケーションをURLで公開するために使用される構成**である。これは外部サービスを使用しても行うことができるが、アプリケーションを公開するための正しい方法であることに注意。 - Ingressを実装する場合、**Ingress Controllers**を作成する必要がある。Ingress Controllerは、リクエストを受け取り、それをチェックし、サービスに負荷分散するエンドポイントとなる**ポッド**である。Ingress Controllerは**構成されたIngressルールに基づいてリクエストを送信する**。Ingressルールは、異なるパスや異なる内部Kubernetesサービスへのサブドメインを指すことができることに注意。 - より良いセキュリティプラクティスは、Kubernetesクラスターのどの部分も公開しないように、エントリーポイントとしてクラウドロードバランサーまたはプロキシサーバーを使用することである。 - どのIngressルールにも一致しないリクエストが受信されると、Ingress Controllerはそれを「**デフォルトバックエンド**」に送る。`describe`コマンドを使用してIngress Controllerのこのパラメータのアドレスを取得できる。 - `minikube addons enable ingress` ### PKIインフラストラクチャ - 証明書機関CA: ![](https://sickrov.github.io/media/Screenshot-66.jpg) - CAはクラスター内のすべての証明書の信頼されたルートである。 - コンポーネントが互いに検証できるようにする。 - すべてのクラスター証明書はCAによって署名される。 - etcdは独自の証明書を持つ。 - タイプ: - apiserver cert. - kubelet cert. - scheduler cert. ## 基本的なアクション ### Minikube **Minikube**は、完全なKubernetes環境をデプロイすることなく、Kubernetesでいくつかの**クイックテスト**を実行するために使用できる。**マスターとノードプロセスを1台のマシンで実行する**。Minikubeはノードを実行するためにVirtualBoxを使用する。**インストール方法は** [**こちら**](https://minikube.sigs.k8s.io/docs/start/) **を参照。** ``` $ minikube start 😄 minikube v1.19.0 on Ubuntu 20.04 ✨ Automatically selected the virtualbox driver. Other choices: none, ssh 💿 Downloading VM boot image ... > minikube-v1.19.0.iso.sha256: 65 B / 65 B [-------------] 100.00% ? p/s 0s > minikube-v1.19.0.iso: 244.49 MiB / 244.49 MiB 100.00% 1.78 MiB p/s 2m17. 👍 Starting control plane node minikube in cluster minikube 💾 Downloading Kubernetes v1.20.2 preload ... > preloaded-images-k8s-v10-v1...: 491.71 MiB / 491.71 MiB 100.00% 2.59 MiB 🔥 Creating virtualbox VM (CPUs=2, Memory=3900MB, Disk=20000MB) ... 🐳 Preparing Kubernetes v1.20.2 on Docker 20.10.4 ... ▪ Generating certificates and keys ... ▪ Booting up control plane ... ▪ Configuring RBAC rules ... 🔎 Verifying Kubernetes components... ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5 🌟 Enabled addons: storage-provisioner, default-storageclass 🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by defaul $ minikube status host: Running kubelet: Running apiserver: Running kubeconfig: Configured ---- ONCE YOU HAVE A K8 SERVICE RUNNING WITH AN EXTERNAL SERVICE ----- $ minikube service mongo-express-service (This will open your browser to access the service exposed port) $ minikube delete 🔥 Deleting "minikube" in virtualbox ... 💀 Removed all traces of the "minikube" cluster ``` ### Kubectlの基本 **`Kubectl`** はKubernetesクラスター用のコマンドラインツールです。これは、Kubernetes内でアクションを実行したりデータを要求したりするために、マスタープロセスのAPIサーバーと通信します。 ```bash kubectl version #Get client and server version kubectl get pod kubectl get services kubectl get deployment kubectl get replicaset kubectl get secret kubectl get all kubectl get ingress kubectl get endpoints #kubectl create deployment --image= kubectl create deployment nginx-deployment --image=nginx #Access the configuration of the deployment and modify it #kubectl edit deployment kubectl edit deployment nginx-deployment #Get the logs of the pod for debbugging (the output of the docker container running) #kubectl logs kubectl logs nginx-deployment-84cd76b964 #kubectl describe pod kubectl describe pod mongo-depl-5fd6b7d4b4-kkt9q #kubectl exec -it -- bash kubectl exec -it mongo-depl-5fd6b7d4b4-kkt9q -- bash #kubectl describe service kubectl describe service mongodb-service #kubectl delete deployment kubectl delete deployment mongo-depl #Deploy from config file kubectl apply -f deployment.yml ``` ### Minikube ダッシュボード ダッシュボードを使用すると、minikube が何を実行しているかをより簡単に確認できます。アクセスするための URL は次の場所にあります: ``` minikube dashboard --url 🔌 Enabling dashboard ... ▪ Using image kubernetesui/dashboard:v2.3.1 ▪ Using image kubernetesui/metrics-scraper:v1.0.7 🤔 Verifying dashboard health ... 🚀 Launching proxy ... 🤔 Verifying proxy health ... http://127.0.0.1:50034/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/ ``` ### YAML構成ファイルの例 各構成ファイルには3つの部分があります: **metadata**、**specification**(起動する必要があるもの)、**status**(望ましい状態)。\ デプロイメント構成ファイルの仕様の中には、実行するイメージを定義する新しい構成構造で定義されたテンプレートがあります: **同じ構成ファイルで宣言されたDeployment + Serviceの例(** [**こちら**](https://gitlab.com/nanuchi/youtube-tutorial-series/-/blob/master/demo-kubernetes-components/mongo.yaml)**から)** サービスは通常1つのデプロイメントに関連しているため、同じ構成ファイルで両方を宣言することが可能です(この構成で宣言されたサービスは内部からのみアクセス可能です): ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: mongodb-deployment labels: app: mongodb spec: replicas: 1 selector: matchLabels: app: mongodb template: metadata: labels: app: mongodb spec: containers: - name: mongodb image: mongo ports: - containerPort: 27017 env: - name: MONGO_INITDB_ROOT_USERNAME valueFrom: secretKeyRef: name: mongodb-secret key: mongo-root-username - name: MONGO_INITDB_ROOT_PASSWORD valueFrom: secretKeyRef: name: mongodb-secret key: mongo-root-password --- apiVersion: v1 kind: Service metadata: name: mongodb-service spec: selector: app: mongodb ports: - protocol: TCP port: 27017 targetPort: 27017 ``` **外部サービス構成の例** このサービスは外部からアクセス可能です(`nodePort` と `type: LoadBlancer` 属性を確認してください): ```yaml --- apiVersion: v1 kind: Service metadata: name: mongo-express-service spec: selector: app: mongo-express type: LoadBalancer ports: - protocol: TCP port: 8081 targetPort: 8081 nodePort: 30000 ``` > [!NOTE] > これはテストに役立ちますが、本番環境では内部サービスのみを持ち、アプリケーションを公開するためのIngressを持つべきです。 **Ingress構成ファイルの例** これにより、アプリケーションが`http://dashboard.com`で公開されます。 ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: dashboard-ingress namespace: kubernetes-dashboard spec: rules: - host: dashboard.com http: paths: - backend: serviceName: kubernetes-dashboard servicePort: 80 ``` **シークレット構成ファイルの例** パスワードがB64でエンコードされていることに注意してください(これは安全ではありません!) ```yaml apiVersion: v1 kind: Secret metadata: name: mongodb-secret type: Opaque data: mongo-root-username: dXNlcm5hbWU= mongo-root-password: cGFzc3dvcmQ= ``` **ConfigMapの例** **ConfigMap**は、ポッドに与えられる設定であり、ポッドが他のサービスをどのように見つけてアクセスするかを知るためのものです。この場合、各ポッドは、`mongodb-service`という名前が、通信できるポッドのアドレスであることを知っています(このポッドはmongodbを実行します): ```yaml apiVersion: v1 kind: ConfigMap metadata: name: mongodb-configmap data: database_url: mongodb-service ``` その後、**deployment config**内で、このアドレスは次のように指定でき、ポッドのenv内にロードされます: ```yaml [...] spec: [...] template: [...] spec: containers: - name: mongo-express image: mongo-express ports: - containerPort: 8081 env: - name: ME_CONFIG_MONGODB_SERVER valueFrom: configMapKeyRef: name: mongodb-configmap key: database_url [...] ``` **ボリューム設定の例** さまざまなストレージ構成のyamlファイルの例は、[https://gitlab.com/nanuchi/youtube-tutorial-series/-/tree/master/kubernetes-volumes](https://gitlab.com/nanuchi/youtube-tutorial-series/-/tree/master/kubernetes-volumes)で見つけることができます。\ **ボリュームは名前空間内には存在しないことに注意してください** ### 名前空間 Kubernetesは、同じ物理クラスターにバックアップされた**複数の仮想クラスター**をサポートしています。これらの仮想クラスターは**名前空間**と呼ばれます。これは、多くのユーザーが複数のチームやプロジェクトに分散している環境での使用を意図しています。数人から十数人のユーザーがいるクラスターでは、名前空間を作成したり考えたりする必要はありません。Kubernetesにデプロイされたアプリケーションの各部分をより良く制御し、整理するために名前空間を使用し始めるべきです。 名前空間は名前のスコープを提供します。リソースの名前は名前空間内で一意である必要がありますが、名前空間間では一意である必要はありません。名前空間は互いにネストすることはできず、**各**Kubernetes **リソース**は**1つの** **名前空間**のみに**存在**できます。 minikubeを使用している場合、デフォルトで4つの名前空間があります: ``` kubectl get namespace NAME STATUS AGE default Active 1d kube-node-lease Active 1d kube-public Active 1d kube-system Active 1d ``` - **kube-system**: ユーザーが使用するためのものではなく、触れるべきではありません。マスターとkubectlプロセス用です。 - **kube-public**: 公開アクセス可能なデータ。クラスター情報を含むconfigmapが含まれています。 - **kube-node-lease**: ノードの可用性を決定します。 - **default**: ユーザーがリソースを作成するために使用する名前空間です。 ```bash #Create namespace kubectl create namespace my-namespace ``` > [!NOTE] > 注意すべきは、ほとんどのKubernetesリソース(例:ポッド、サービス、レプリケーションコントローラーなど)は、いくつかのネームスペースに存在することです。しかし、ネームスペースリソースやノード、persistentVolumesなどの低レベルリソースはネームスペースに存在しません。どのKubernetesリソースがネームスペースにあり、どれがないかを確認するには: > > ```bash > kubectl api-resources --namespaced=true #ネームスペース内 > kubectl api-resources --namespaced=false #ネームスペース外 > ``` そのコンテキスト内のすべての後続のkubectlコマンドに対してネームスペースを保存できます。 ```bash kubectl config set-context --current --namespace= ``` ### Helm HelmはKubernetesの**パッケージマネージャー**です。YAMLファイルをパッケージ化し、公開およびプライベートリポジトリで配布することを可能にします。これらのパッケージは**Helm Charts**と呼ばれます。 ``` helm search ``` Helmは、変数を使用して設定ファイルを生成するテンプレートエンジンでもあります。 ## Kubernetesシークレット **Secret**は、パスワード、トークン、またはキーなどの**機密データを含む**オブジェクトです。このような情報は、Pod仕様やイメージに配置される可能性があります。ユーザーはSecretsを作成でき、システムもSecretsを作成します。Secretオブジェクトの名前は有効な**DNSサブドメイン名**でなければなりません。ここで[公式ドキュメント](https://kubernetes.io/docs/concepts/configuration/secret/)を読むことができます。 Secretsは以下のようなものです: - API、SSHキー。 - OAuthトークン。 - 資格情報、パスワード(プレーンテキストまたはb64 + 暗号化)。 - 情報やコメント。 - データベース接続コード、文字列… 。 Kubernetesには異なるタイプのシークレットがあります。 | ビルトインタイプ | 使用法 | | ----------------------------------- | ----------------------------------------- | | **Opaque** | **任意のユーザー定義データ(デフォルト)** | | kubernetes.io/service-account-token | サービスアカウントトークン | | kubernetes.io/dockercfg | シリアライズされた\~/.dockercfgファイル | | kubernetes.io/dockerconfigjson | シリアライズされた\~/.docker/config.jsonファイル | | kubernetes.io/basic-auth | ベーシック認証のための資格情報 | | kubernetes.io/ssh-auth | SSH認証のための資格情報 | | kubernetes.io/tls | TLSクライアントまたはサーバーのためのデータ | | bootstrap.kubernetes.io/token | ブートストラップトークンデータ | > [!NOTE] > **Opaqueタイプはデフォルトであり、ユーザーによって定義された典型的なキーと値のペアです。** **シークレットの動作:** ![](https://sickrov.github.io/media/Screenshot-164.jpg) 以下の設定ファイルは、`mysecret`という**シークレット**を定義し、2つのキーと値のペア`username: YWRtaW4=`と`password: MWYyZDFlMmU2N2Rm`を持っています。また、`mysecret`で定義された`username`と`password`が**環境変数**`SECRET_USERNAME` \_\_ と \_\_ `SECRET_PASSWOR`に公開される`secretpod`という**pod**も定義しています。さらに、`mysecret`内の`username`シークレットをパス`/etc/foo/my-group/my-username`に`0640`の権限で**マウント**します。 ```yaml:secretpod.yaml apiVersion: v1 kind: Secret metadata: name: mysecret type: Opaque data: username: YWRtaW4= password: MWYyZDFlMmU2N2Rm --- apiVersion: v1 kind: Pod metadata: name: secretpod spec: containers: - name: secretpod image: nginx env: - name: SECRET_USERNAME valueFrom: secretKeyRef: name: mysecret key: username - name: SECRET_PASSWORD valueFrom: secretKeyRef: name: mysecret key: password volumeMounts: - name: foo mountPath: "/etc/foo" restartPolicy: Never volumes: - name: foo secret: secretName: mysecret items: - key: username path: my-group/my-username mode: 0640 ``` ```bash kubectl apply -f kubectl get pods #Wait until the pod secretpod is running kubectl exec -it secretpod -- bash env | grep SECRET && cat /etc/foo/my-group/my-username && echo ``` ### Secrets in etcd **etcd**は、すべてのクラスターデータのKubernetesバックストアとして使用される、一貫性があり高可用性の**キー-バリューストア**です。etcdに保存されている秘密にアクセスしてみましょう: ```bash cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep etcd ``` 証明書、キー、URLがファイルシステムにどこにあるかを見ることができます。それを取得すると、etcdに接続できるようになります。 ```bash #ETCDCTL_API=3 etcdctl --cert --key --cacert endpoint=[] health ETCDCTL_API=3 etcdctl --cert /etc/kubernetes/pki/apiserver-etcd-client.crt --key /etc/kubernetes/pki/apiserver-etcd-client.key --cacert /etc/kubernetes/pki/etcd/etcd/ca.cert endpoint=[127.0.0.1:1234] health ``` 通信が確立されると、秘密を取得できるようになります: ```bash #ETCDCTL_API=3 etcdctl --cert --key --cacert endpoint=[] get ETCDCTL_API=3 etcdctl --cert /etc/kubernetes/pki/apiserver-etcd-client.crt --key /etc/kubernetes/pki/apiserver-etcd-client.key --cacert /etc/kubernetes/pki/etcd/etcd/ca.cert endpoint=[127.0.0.1:1234] get /registry/secrets/default/secret_02 ``` **ETCDへの暗号化の追加** デフォルトでは、すべてのシークレットは**プレーン**テキストでetcd内に保存されますが、暗号化レイヤーを適用しない限りそうなります。以下の例は[https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/](https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/)に基づいています。 ```yaml:encryption.yaml apiVersion: apiserver.config.k8s.io/v1 kind: EncryptionConfiguration resources: - resources: - secrets providers: - aescbc: keys: - name: key1 secret: cjjPMcWpTPKhAdieVtd+KhG4NN+N6e3NmBPMXJvbfrY= #Any random key - identity: {} ``` その後、作成した設定ファイルの場所を指すように `kube-apiserver` の `--encryption-provider-config` フラグを設定する必要があります。 `/etc/kubernetes/manifest/kube-apiserver.yaml` を修正し、次の行を追加できます: ```yaml containers: - command: - kube-apiserver - --encriyption-provider-config=/etc/kubernetes/etcd/ ``` ボリュームマウントの中をスクロールダウンしてください: ```yaml - mountPath: /etc/kubernetes/etcd name: etcd readOnly: true ``` ボリュームマウントの中でhostPathまでスクロールします: ```yaml - hostPath: path: /etc/kubernetes/etcd type: DirectoryOrCreate name: etcd ``` **データが暗号化されていることの確認** データはetcdに書き込まれるときに暗号化されます。`kube-apiserver`を再起動した後、新しく作成または更新されたシークレットは、保存される際に暗号化されるべきです。確認するには、`etcdctl`コマンドラインプログラムを使用してシークレットの内容を取得できます。 1. `default`名前空間に`secret1`という新しいシークレットを作成します: ``` kubectl create secret generic secret1 -n default --from-literal=mykey=mydata ``` 2. etcdctlコマンドラインを使用して、そのシークレットをetcdから読み取ります: `ETCDCTL_API=3 etcdctl get /registry/secrets/default/secret1 [...] | hexdump -C` ここで`[...]`はetcdサーバーに接続するための追加の引数でなければなりません。 3. 保存されたシークレットが`k8s:enc:aescbc:v1:`で始まることを確認します。これは`aescbc`プロバイダーが結果のデータを暗号化したことを示します。 4. APIを介して取得したときにシークレットが正しく復号化されていることを確認します: ``` kubectl describe secret secret1 -n default ``` は`mykey: bXlkYXRh`と一致するべきです。mydataはエンコードされているため、シークレットを完全にデコードするには[シークレットのデコード](https://kubernetes.io/docs/concepts/configuration/secret#decoding-a-secret)を確認してください。 **シークレットは書き込み時に暗号化されるため、シークレットの更新を行うとその内容が暗号化されます:** ``` kubectl get secrets --all-namespaces -o json | kubectl replace -f - ``` **最終的なヒント:** - FSに秘密を保持しないようにし、他の場所から取得してください。 - あなたの秘密にさらなる保護を追加するために[https://www.vaultproject.io/](https://www.vaultproject.io)をチェックしてください。 - [https://kubernetes.io/docs/concepts/configuration/secret/#risks](https://kubernetes.io/docs/concepts/configuration/secret/#risks) - [https://docs.cyberark.com/Product-Doc/OnlineHelp/AAM-DAP/11.2/en/Content/Integrations/Kubernetes_deployApplicationsConjur-k8s-Secrets.htm](https://docs.cyberark.com/Product-Doc/OnlineHelp/AAM-DAP/11.2/en/Content/Integrations/Kubernetes_deployApplicationsConjur-k8s-Secrets.htm) ## 参考文献 {{#ref}} https://sickrov.github.io/ {{#endref}} {{#ref}} https://www.youtube.com/watch?v=X48VuDVv0do {{#endref}} {{#include ../../banners/hacktricks-training.md}}