diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index db00f3c..9b5588e 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -28,18 +28,14 @@ jobs: strategy: fail-fast: false matrix: - skupper-version: [latest, main] + skupper-version: [2.0.0-preview-1] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.x" - - uses: manusa/actions-setup-minikube@v2.10.0 - with: - minikube version: "v1.32.0" - kubernetes version: "v1.29.0" - github token: ${{secrets.GITHUB_TOKEN}} + - uses: medyagh/setup-minikube@latest - run: curl https://skupper.io/install.sh | bash -s -- --version ${{matrix.skupper-version}} - run: echo "$HOME/.local/bin" >> "$GITHUB_PATH" - run: ./plano test diff --git a/README.md b/README.md index 255e6d7..f65c809 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ + + # Skupper Hello World using YAML [![main](https://github.com/ssorj/skupper-example-yaml/actions/workflows/main.yaml/badge.svg)](https://github.com/ssorj/skupper-example-yaml/actions/workflows/main.yaml) @@ -15,13 +17,12 @@ across cloud providers, data centers, and edge sites. * [Overview](#overview) * [Prerequisites](#prerequisites) -* [Step 1: Install Skupper in your clusters](#step-1-install-skupper-in-your-clusters) -* [Step 2: Set up your namespaces](#step-2-set-up-your-namespaces) +* [Step 1: Set up your clusters](#step-1-set-up-your-clusters) +* [Step 2: Install Skupper on your clusters](#step-2-install-skupper-on-your-clusters) * [Step 3: Apply your YAML resources](#step-3-apply-your-yaml-resources) * [Step 4: Link your sites](#step-4-link-your-sites) * [Step 5: Access the frontend](#step-5-access-the-frontend) * [Cleaning up](#cleaning-up) -* [Summary](#summary) * [Next steps](#next-steps) * [About this example](#about-this-example) @@ -63,51 +64,12 @@ services without exposing the backend to the public internet. [install-kubectl]: https://kubernetes.io/docs/tasks/tools/install-kubectl/ [kube-providers]: https://skupper.io/start/kubernetes.html -## Step 1: Install Skupper in your clusters - -Use the `kubectl apply` command to install the Skupper -controller in each cluster. - -_**West:**_ - -~~~ shell -kubectl apply -f skupper.yaml -~~~ - -_Sample output:_ - -~~~ console -$ kubectl apply -f skupper.yaml -namespace/skupper-site-controller created -serviceaccount/skupper-site-controller created -clusterrole.rbac.authorization.k8s.io/skupper-site-controller created -clusterrolebinding.rbac.authorization.k8s.io/skupper-site-controller created -deployment.apps/skupper-site-controller created -~~~ - -_**East:**_ +## Step 1: Set up your clusters -~~~ shell -kubectl apply -f skupper.yaml -~~~ - -_Sample output:_ - -~~~ console -$ kubectl apply -f skupper.yaml -namespace/skupper-site-controller created -serviceaccount/skupper-site-controller created -clusterrole.rbac.authorization.k8s.io/skupper-site-controller created -clusterrolebinding.rbac.authorization.k8s.io/skupper-site-controller created -deployment.apps/skupper-site-controller created -~~~ - -## Step 2: Set up your namespaces - -Skupper is designed for use with multiple Kubernetes namespaces, -usually on different clusters. The `skupper` and `kubectl` -commands use your [kubeconfig][kubeconfig] and current context to -select the namespace where they operate. +Skupper is designed for use with multiple Kubernetes clusters. +The `skupper` and `kubectl` commands use your +[kubeconfig][kubeconfig] and current context to select the cluster +and namespace where they operate. [kubeconfig]: https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/ @@ -152,6 +114,27 @@ kubectl create namespace east kubectl config set-context --current --namespace east ~~~ +## Step 2: Install Skupper on your clusters + +Using Skupper on Kubernetes requires the installation of the +Skupper custom resource definitions (CRDs) and the Skupper +controller. + +For each cluster, use `kubectl apply` with the Skupper +installation YAML to install the CRDs and controller. + +_**West:**_ + +~~~ shell +kubectl apply -f https://skupper.io/v2/install.yaml +~~~ + +_**East:**_ + +~~~ shell +kubectl apply -f https://skupper.io/v2/install.yaml +~~~ + ## Step 3: Apply your YAML resources To configure our example sites and service bindings, we are @@ -181,12 +164,13 @@ information. [site.yaml](west/site.yaml): ~~~ yaml -apiVersion: v1 -kind: ConfigMap +apiVersion: skupper.io/v1alpha1 +kind: Site metadata: - name: skupper-site -data: name: west + namespace: west +spec: + linkAccess: default ~~~ #### Resources in East @@ -198,13 +182,11 @@ ingress is required at this site for the Hello World example. [site.yaml](east/site.yaml): ~~~ yaml -apiVersion: v1 -kind: ConfigMap +apiVersion: skupper.io/v1alpha1 +kind: Site metadata: - name: skupper-site -data: name: east - ingress: none + namespace: east ~~~ In East, the `backend` deployment has an annotation named @@ -251,13 +233,15 @@ Skupper sites. _**West:**_ ~~~ shell -kubectl apply -f west/site.yaml -f west/frontend.yaml +kubectl apply -f west/site.yaml -f west/frontend.yaml -f west/listener.yaml +kubectl wait site/west --for condition=Ready --timeout 120s ~~~ _Sample output:_ ~~~ console -$ kubectl apply -f west/site.yaml -f west/frontend.yaml +$ kubectl apply -f west/site.yaml -f west/frontend.yaml -f west/listener.yaml +XXX configmap/skupper-site created deployment.apps/frontend created ~~~ @@ -265,13 +249,15 @@ deployment.apps/frontend created _**East:**_ ~~~ shell -kubectl apply -f east/site.yaml -f east/backend.yaml +kubectl apply -f east/site.yaml -f east/backend.yaml -f east/connector.yaml +kubectl wait site/east --for condition=Ready --timeout 120s ~~~ _Sample output:_ ~~~ console -$ kubectl apply -f east/site.yaml -f east/backend.yaml +$ kubectl apply -f east/site.yaml -f east/backend.yaml -f east/connector.yaml +XXX configmap/skupper-site created deployment.apps/backend created ~~~ @@ -297,7 +283,7 @@ secret token in West and create the link in East. To install the Skupper command: ~~~ shell -curl https://skupper.io/install.sh | sh +curl https://skupper.io/install.sh | sh -s -- --version 2.0.0-preview-1 ~~~ For more installation options, see [Installing @@ -312,28 +298,39 @@ East to create a link. _**West:**_ ~~~ shell -skupper token create ~/secret.token +skupper token issue ~/token.yaml ~~~ _Sample output:_ ~~~ console -$ skupper token create ~/secret.token -Token written to ~/secret.token +$ skupper token issue ~/token.yaml +Waiting for token status ... + +Grant "west-cad4f72d-2917-49b9-ab66-cdaca4d6cf9c" is ready +Token file /run/user/1000/skewer/token.yaml created + +Transfer this file to a remote site. At the remote site, +create a link to this site using the "skupper token redeem" command: + + skupper token redeem + +The token expires after 1 use(s) or after 15m0s. ~~~ _**East:**_ ~~~ shell -skupper link create ~/secret.token +skupper token redeem ~/token.yaml ~~~ _Sample output:_ ~~~ console -$ skupper link create ~/secret.token -Site configured to link to https://10.105.193.154:8081/ed9c37f6-d78a-11ec-a8c7-04421a4c5042 (name=link1) -Check the status of the link using 'skupper link status'. +$ skupper token redeem ~/token.yaml +Waiting for token status ... +Token "west-cad4f72d-2917-49b9-ab66-cdaca4d6cf9c" has been redeemed +You can now safely delete /run/user/1000/skewer/token.yaml ~~~ If your terminal sessions are on different machines, you may need @@ -346,43 +343,17 @@ creation. In order to use and test the application, we need external access to the frontend. -Use `kubectl expose` with `--type LoadBalancer` to open network -access to the frontend service. - -Once the frontend is exposed, use `kubectl get service/frontend` -to look up the external IP of the frontend service. If the -external IP is ``, try again after a moment. - -Once you have the external IP, use `curl` or a similar tool to -request the `/api/health` endpoint at that address. - -**Note:** The `` field in the following commands is a -placeholder. The actual value is an IP address. +Use `kubectl port-forward` to make the frontend available at +`localhost:8080`. _**West:**_ ~~~ shell -kubectl expose deployment/frontend --port 8080 --type LoadBalancer -kubectl get service/frontend -curl http://:8080/api/health -~~~ - -_Sample output:_ - -~~~ console -$ kubectl expose deployment/frontend --port 8080 --type LoadBalancer -service/frontend exposed - -$ kubectl get service/frontend -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -frontend LoadBalancer 10.103.232.28 8080:30407/TCP 15s - -$ curl http://:8080/api/health -OK +kubectl port-forward deployment/frontend 8080:8080 ~~~ -If everything is in order, you can now access the web interface by -navigating to `http://:8080/` in your browser. +You can now access the web interface by navigating to +[http://localhost:8080](http://localhost:8080) in your browser. ## Cleaning up @@ -392,15 +363,15 @@ the following commands. _**West:**_ ~~~ shell -kubectl delete -f west/site.yaml -f west/frontend.yaml -kubectl delete -f skupper.yaml +kubectl delete -f west/site.yaml -f west/frontend.yaml -f west/listener.yaml +kubectl delete -f https://skupper.io/v2/install.yaml ~~~ _**East:**_ ~~~ shell -kubectl delete -f east/site.yaml -f east/backend.yaml -kubectl delete -f skupper.yaml +kubectl delete -f east/site.yaml -f east/backend.yaml -f east/connector.yaml +kubectl delete -f https://skupper.io/v2/install.yaml ~~~ ## Next steps diff --git a/east/backend.yaml b/east/backend.yaml index 06b1018..fa2cfb5 100644 --- a/east/backend.yaml +++ b/east/backend.yaml @@ -4,8 +4,6 @@ metadata: name: backend labels: app: backend - annotations: - skupper.io/proxy: tcp spec: selector: matchLabels: diff --git a/east/connector.yaml b/east/connector.yaml new file mode 100644 index 0000000..7c40f60 --- /dev/null +++ b/east/connector.yaml @@ -0,0 +1,9 @@ +apiVersion: skupper.io/v1alpha1 +kind: Connector +metadata: + name: backend + namespace: east +spec: + routingKey: backend + port: 8080 + selector: app=backend diff --git a/east/site.yaml b/east/site.yaml index ea40e2b..e3e63f8 100644 --- a/east/site.yaml +++ b/east/site.yaml @@ -1,7 +1,5 @@ -apiVersion: v1 -kind: ConfigMap +apiVersion: skupper.io/v1alpha1 +kind: Site metadata: - name: skupper-site -data: name: east - ingress: none + namespace: east diff --git a/external/skewer/.github/workflows/main.yaml b/external/skewer/.github/workflows/main.yaml index dbe7435..fcb7aea 100644 --- a/external/skewer/.github/workflows/main.yaml +++ b/external/skewer/.github/workflows/main.yaml @@ -5,22 +5,18 @@ on: schedule: - cron: "0 0 * * 0" jobs: - main: + test: strategy: fail-fast: false matrix: - skupper-version: [latest, main] + skupper-version: [2.0.0-preview-1] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.x" - - uses: manusa/actions-setup-minikube@v2.10.0 - with: - minikube version: "v1.32.0" - kubernetes version: "v1.29.0" - github token: ${{secrets.GITHUB_TOKEN}} + - uses: medyagh/setup-minikube@latest - run: curl https://skupper.io/install.sh | bash -s -- --version ${{matrix.skupper-version}} - run: echo "$HOME/.local/bin" >> $GITHUB_PATH - run: ./plano test diff --git a/external/skewer/README.md b/external/skewer/README.md index f81793d..039fa6b 100644 --- a/external/skewer/README.md +++ b/external/skewer/README.md @@ -8,6 +8,15 @@ A `skewer.yaml` file describes the steps and commands to achieve an objective using Skupper. Skewer takes the `skewer.yaml` file as input and produces two outputs: a `README.md` file and a test routine. +#### Contents + +* [An example example](#an-example-example) +* [Setting up Skewer for your own example](#setting-up-skewer-for-your-own-example) +* [Skewer YAML](#skewer-yaml) +* [Standard steps](#standard-steps) +* [Demo mode](#demo-mode) +* [Troubleshooting](#troubleshooting) + ## An example example [Example `skewer.yaml` file](example/skewer.yaml) @@ -89,7 +98,7 @@ commands: ## Skewer YAML -The top level: +The top level of the `skewer.yaml` file: ~~~ yaml title: # Your example's title (required) @@ -103,7 +112,18 @@ summary: # Text to summarize what the user did (optional) next_steps: # Text linking to more examples (optional, has default text) ~~~ -To disable the GitHub workflow, set it to `null`. +For fields with default text such as `prerequisites` and `next_steps`, +you can include the default text inside your custom text by using the +`@default@` placeholder: + +~~~ yaml +next_steps: + @default@ + + This Way to the Egress. +~~~ + +To disable the GitHub workflow and CI badge, set `workflow` to `null`. A **site**: @@ -167,79 +187,25 @@ steps: west: ~~~ -Or you can use a named step from the library of standard steps: - -~~~ yaml -- standard: configure_separate_console_sessions -~~~ - -The standard steps are defined in -[python/skewer/standardsteps.yaml](python/skewer/standardsteps.yaml). -Note that you should not edit this file. Instead, in your -`skewer.yaml` file, you can create custom steps based on the standard -steps. You can override the `title`, `preamble`, `commands`, or -`postamble` field of a standard step by adding the field in addition -to `standard`: - -~~~ yaml -- standard: cleaning_up - commands: - east: - - run: skupper delete - - run: kubectl delete deployment/database - west: - - run: skupper delete -~~~ - -A typical mix of standard and custom steps might look like this: - -~~~ yaml -steps: - - standard: install_the_skupper_command_line_tool - - standard: kubernetes/set_up_your_namespaces - - - standard: kubernetes/create_your_sites - - standard: link_your_sites - - - - standard: cleaning_up -~~~ - -**Note:** The `link_your_sites`, `access_the_application`, and -`cleaning_up` steps are less generic than the other steps. -`link_your_sites` assumes just two sites. `access_the_application` -assumes you have a `frontend` service. `cleaning_up` doesn't delete -any application workoads. Check that the text and commands these -steps produce are doing what you need for your example. If not, you -need to provide a custom step. - -There are some standard steps for examples based on the Skupper -Hello World application: - -~~~ yaml -- standard: hello_world/deploy_the_frontend_and_backend -- standard: hello_world/expose_the_backend -- standard: hello_world/access_the_frontend -- standard: hello_world/cleaning_up -~~~ - -And finally there are some special cases: -~~~ yaml -- standard: kubernetes/set_up_your_kubernetes_namespace -- standard: podman/set_up_your_podman_network -~~~ - The step commands are separated into named groups corresponding to the sites. Each named group contains a list of command entries. Each command entry has a `run` field containing a shell command and other fields for awaiting completion or providing sample output. +You can also use a named step from the library of [standard +steps](#standard-steps): + +~~~ yaml +- standard: kubernetes/set_up_your_clusters +~~~ + A **command**: ~~~ yaml - run: # A shell command (required) - apply: # Use this command only for "readme" or "test" (optional, default is both) + apply: # Use this command only for "readme" or "test" (default is both) output: # Sample output to include in the README (optional) + expect_failure: # If true, check that the command fails and keep going (default false) ~~~ Only the `run` and `output` fields are used in the README content. @@ -257,8 +223,8 @@ used only for testing and do not impact the README. ~~~ yaml - await_resource: # A resource for which to await readiness (optional) # Example: await_resource: deployment/frontend -- await_external_ip: # A service for which to await an external IP (optional) - # Example: await_service: service/frontend +- await_ingress: # A service for which to await an external hostname or IP (optional) + # Example: await_ingress: service/frontend - await_http_ok: # A service and URL template for which to await an HTTP OK response (optional) # Example: await_http_ok: [service/frontend, "http://{}:8080/api/hello"] ~~~ @@ -268,9 +234,9 @@ Example commands: ~~~ yaml commands: east: - - run: kubectl expose deployment/backend --port 8080 --type LoadBalancer + - run: skupper expose deployment/backend --port 8080 output: | - service/frontend exposed + deployment backend exposed as backend west: - await_resource: service/backend - run: kubectl get service/backend @@ -279,6 +245,83 @@ commands: backend ClusterIP 10.102.112.121 8080/TCP 30s ~~~ +## Standard steps + +Skewer includes a library of standard steps with descriptive text and +commands that we use a lot for our examples. + +The standard steps are defined in +[python/skewer/standardsteps.yaml](python/skewer/standardsteps.yaml). +They are the following: + +~~~ +general/install_the_skupper_command_line_tool +general/link_your_sites +general/cleaning_up +kubernetes/set_up_your_clusters +kubernetes/set_up_your_kubernetes_cluster # One cluster only +kubernetes/create_your_sites +kubernetes/link_your_sites +kubernetes/access_the_frontend +kubernetes/cleaning_up +podman/set_up_your_podman_environment +hello_world/deploy_the_frontend_and_backend +hello_world/expose_the_backend +hello_world/access_the_frontend +hello_world/cleaning_up +~~~ + +The `general` steps are generic (or pretty generic) with respect to +platform and application. The `kubernetes` and `podman` steps are +coupled to their platform. The `hello_world` steps are specific to +the Skupper Hello World application. + +**Note:** The `link_your_sites` and `cleaning_up` steps are less +generic than the other `general` steps. For example, `cleaning_up` +doesn't delete any application workoads. Check that the text and +commands these steps produce are doing what you need for your example. +If not, you need to provide a custom step. + +You can create custom steps based on the standard steps by overriding +the `title`, `preamble`, `commands`, or `postamble` fields. + +~~~ yaml +- standard: kubernetes/cleaning_up + commands: + east: + - run: skupper delete + - run: kubectl delete deployment/database + west: + - run: skupper delete +~~~ + +For string fields such as `preamble` and `postamble`, you can include +the standard text inside your custom text by using the `@default@` +placeholder: + +~~~ yaml +- standard: general/cleaning_up + preamble: | + @default@ + + Note: You may also want to flirp your krupke. +~~~ + +A typical mix of standard and custom steps for a Kubernetes-based +example might look like this: + +~~~ yaml +steps: + - standard: general/install_the_skupper_command_line_tool + - standard: kubernetes/set_up_your_clusters + + - standard: kubernetes/create_your_sites + - standard: kubernetes/link_your_sites + + + - standard: kubernetes/cleaning_up +~~~ + ## Demo mode Skewer has a mode where it executes all the steps, but before cleaning @@ -287,3 +330,24 @@ up and exiting, it pauses so you can inspect things. It is enabled by setting the environment variable `SKEWER_DEMO` to any value when you call `./plano run` or one of its variants. You can also use `./plano demo`, which sets the variable for you. + +## Troubleshooting + +### Subnet is already used + +Error: + +~~~ console +plano: notice: Starting Minikube +plano: notice: Running command 'minikube start -p skewer --auto-update-drivers false' +* Creating podman container (CPUs=2, Memory=16000MB) ...- E0229 05:44:29.821273 12224 network_create.go:113] error while trying to create podman network skewer 192.168.49.0/24: create podman network skewer 192.168.49.0/24 with gateway 192.168.49.1 and MTU of 0: sudo -n podman network create --driver=bridge --subnet=192.168.49.0/24 --gateway=192.168.49.1 --label=created_by.minikube.sigs.k8s.io=true --label=name.minikube.sigs.k8s.io=skewer skewer: exit status 125 + +Error: subnet 192.168.49.0/24 is already used on the host or by another config +~~~ + +Remove the existing Podman network. Note that it might belong to +another user on the host. + +~~~ shell +sudo podman network rm minikube +~~~ diff --git a/external/skewer/config/.github/workflows/main.yaml b/external/skewer/config/.github/workflows/main.yaml index db00f3c..ca8214b 100644 --- a/external/skewer/config/.github/workflows/main.yaml +++ b/external/skewer/config/.github/workflows/main.yaml @@ -35,11 +35,7 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.x" - - uses: manusa/actions-setup-minikube@v2.10.0 - with: - minikube version: "v1.32.0" - kubernetes version: "v1.29.0" - github token: ${{secrets.GITHUB_TOKEN}} + - uses: medyagh/setup-minikube@latest - run: curl https://skupper.io/install.sh | bash -s -- --version ${{matrix.skupper-version}} - run: echo "$HOME/.local/bin" >> "$GITHUB_PATH" - run: ./plano test diff --git a/external/skewer/example/README.md b/external/skewer/example/README.md index aed9d8d..7fa63b1 100644 --- a/external/skewer/example/README.md +++ b/external/skewer/example/README.md @@ -1,3 +1,5 @@ + + # Skupper Hello World [![main](https://github.com/skupperproject/skewer/actions/workflows/main.yaml/badge.svg)](https://github.com/skupperproject/skewer/actions/workflows/main.yaml) @@ -15,15 +17,16 @@ across cloud providers, data centers, and edge sites. * [Overview](#overview) * [Prerequisites](#prerequisites) -* [Step 1: Install the Skupper command-line tool](#step-1-install-the-skupper-command-line-tool) -* [Step 2: Set up your namespaces](#step-2-set-up-your-namespaces) -* [Step 3: Deploy the frontent and backend](#step-3-deploy-the-frontent-and-backend) -* [Step 4: Create your sites](#step-4-create-your-sites) -* [Step 5: Link your sites](#step-5-link-your-sites) -* [Step 6: Fail on demand](#step-6-fail-on-demand) -* [Step 7: Expose the backend](#step-7-expose-the-backend) -* [Step 8: Access the frontend](#step-8-access-the-frontend) -* [Cleaning up](#cleaning-up) +* [Step 1: Set up your clusters](#step-1-set-up-your-clusters) +* [Step 2: Deploy the frontend and backend](#step-2-deploy-the-frontend-and-backend) +* [Step 3: Install Skupper on your clusters](#step-3-install-skupper-on-your-clusters) +* [Step 4: Install the Skupper command-line tool](#step-4-install-the-skupper-command-line-tool) +* [Step 5: Create your sites](#step-5-create-your-sites) +* [Step 6: Link your sites](#step-6-link-your-sites) +* [Step 7: Fail on demand](#step-7-fail-on-demand) +* [Step 8: Fail expectedly](#step-8-fail-expectedly) +* [Step 9: Expose the backend](#step-9-expose-the-backend) +* [Step 10: Access the frontend](#step-10-access-the-frontend) * [Summary](#summary) * [Next steps](#next-steps) * [About this example](#about-this-example) @@ -36,34 +39,12 @@ An overview Some prerequisites -## Step 1: Install the Skupper command-line tool - -This example uses the Skupper command-line tool to deploy Skupper. -You need to install the `skupper` command only once for each -development environment. +## Step 1: Set up your clusters -On Linux or Mac, you can use the install script (inspect it -[here][install-script]) to download and extract the command: - -~~~ shell -curl https://skupper.io/install.sh | sh -~~~ - -The script installs the command under your home directory. It -prompts you to add the command to your path if necessary. - -For Windows and other installation options, see [Installing -Skupper][install-docs]. - -[install-script]: https://github.com/skupperproject/skupper-website/blob/main/input/install.sh -[install-docs]: https://skupper.io/install/ - -## Step 2: Set up your namespaces - -Skupper is designed for use with multiple Kubernetes namespaces, -usually on different clusters. The `skupper` and `kubectl` -commands use your [kubeconfig][kubeconfig] and current context to -select the namespace where they operate. +Skupper is designed for use with multiple Kubernetes clusters. +The `skupper` and `kubectl` commands use your +[kubeconfig][kubeconfig] and current context to select the cluster +and namespace where they operate. [kubeconfig]: https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/ @@ -108,14 +89,13 @@ kubectl create namespace east kubectl config set-context --current --namespace east ~~~ -## Step 3: Deploy the frontent and backend +## Step 2: Deploy the frontend and backend This example runs the frontend and the backend in separate Kubernetes namespaces, on different clusters. -Use `kubectl create deployment` to deploy the frontend in -namespace `west` and the backend in namespace -`east`. +Use `kubectl create deployment` to deploy the frontend in West +and the backend in East. _**West:**_ @@ -129,107 +109,164 @@ _**East:**_ kubectl create deployment backend --image quay.io/skupper/hello-world-backend --replicas 3 ~~~ -## Step 4: Create your sites +## Step 3: Install Skupper on your clusters + +Using Skupper on Kubernetes requires the installation of the +Skupper custom resource definitions (CRDs) and the Skupper +controller. + +For each cluster, use `kubectl apply` with the Skupper +installation YAML to install the CRDs and controller. + +_**West:**_ + +~~~ shell +kubectl apply -f https://skupper.io/v2/install.yaml +~~~ + +_**East:**_ + +~~~ shell +kubectl apply -f https://skupper.io/v2/install.yaml +~~~ + +## Step 4: Install the Skupper command-line tool + +This example uses the Skupper command-line tool to create Skupper +resources. You need to install the `skupper` command only once +for each development environment. + +On Linux or Mac, you can use the install script (inspect it +[here][install-script]) to download and extract the command: + +~~~ shell +curl https://skupper.io/install.sh | sh -s -- --version 2.0.0-preview-1 +~~~ + +The script installs the command under your home directory. It +prompts you to add the command to your path if necessary. + +For Windows and other installation options, see [Installing +Skupper][install-docs]. + +[install-script]: https://github.com/skupperproject/skupper-website/blob/main/input/install.sh +[install-docs]: https://skupper.io/install/ + +## Step 5: Create your sites A Skupper _site_ is a location where components of your application are running. Sites are linked together to form a network for your application. In Kubernetes, a site is associated with a namespace. -For each namespace, use `skupper init` to create a site. This -deploys the Skupper router and controller. Then use `skupper -status` to see the outcome. +For each namespace, use `skupper site create` with a site name of +your choice. This creates the site resource and deploys the +Skupper router to the namespace. **Note:** If you are using Minikube, you need to [start minikube tunnel][minikube-tunnel] before you run `skupper init`. + + [minikube-tunnel]: https://skupper.io/start/minikube.html#running-minikube-tunnel _**West:**_ ~~~ shell -skupper init -skupper status +skupper site create west --enable-link-access +kubectl wait --for=condition=Ready site/west # Required with preview 1 - to be removed! ~~~ _Sample output:_ ~~~ console -$ skupper init -Waiting for LoadBalancer IP or hostname... +$ skupper site create west --enable-link-access Waiting for status... -Skupper is now installed in namespace 'west'. Use 'skupper status' to get more information. +Site "west" is configured. Check the status to see when it is ready -$ skupper status -Skupper is enabled for namespace "west". It is not connected to any other sites. It has no exposed services. +$ kubectl wait --for=condition=Ready site/west # Required with preview 1 - to be removed! +site.skupper.io/west condition met ~~~ _**East:**_ ~~~ shell -skupper init -skupper status +skupper site create east +kubectl wait --for=condition=Ready site/east # Required with preview 1 - to be removed! ~~~ _Sample output:_ ~~~ console -$ skupper init -Waiting for LoadBalancer IP or hostname... +$ skupper site create east Waiting for status... -Skupper is now installed in namespace 'east'. Use 'skupper status' to get more information. +Site "east" is configured. Check the status to see when it is ready -$ skupper status -Skupper is enabled for namespace "east". It is not connected to any other sites. It has no exposed services. +$ kubectl wait --for=condition=Ready site/east # Required with preview 1 - to be removed! +site.skupper.io/east condition met ~~~ -As you move through the steps below, you can use `skupper status` at -any time to check your progress. +You can use `skupper site status` at any time to check the status +of your site. + +## Step 6: Link your sites -## Step 5: Link your sites +A Skupper _link_ is a channel for communication between two sites. +Links serve as a transport for application connections and +requests. -Creating a link requires use of two `skupper` commands in -conjunction, `skupper token create` and `skupper link create`. +Creating a link requires use of two Skupper commands in +conjunction, `skupper token issue` and `skupper token redeem`. -The `skupper token create` command generates a secret token that +The `skupper token issue` command generates a secret token that signifies permission to create a link. The token also carries the -link details. Then, in a remote site, The `skupper link -create` command uses the token to create a link to the site -that generated it. +link details. Then, in a remote site, The `skupper token redeem` +command uses the token to create a link to the site that generated +it. **Note:** The link token is truly a *secret*. Anyone who has the token can link to your site. Make sure that only those you trust have access to it. -First, use `skupper token create` in site West to generate the -token. Then, use `skupper link create` in site East to link -the sites. +First, use `skupper token issue` in West to generate the token. +Then, use `skupper token redeem` in East to link the sites. _**West:**_ ~~~ shell -skupper token create ~/secret.token +skupper token issue ~/token.yaml ~~~ _Sample output:_ ~~~ console -$ skupper token create ~/secret.token -Token written to ~/secret.token +$ skupper token issue ~/token.yaml +Waiting for token status ... + +Grant "west-cad4f72d-2917-49b9-ab66-cdaca4d6cf9c" is ready +Token file /run/user/1000/skewer/token.yaml created + +Transfer this file to a remote site. At the remote site, +create a link to this site using the "skupper token redeem" command: + + skupper token redeem + +The token expires after 1 use(s) or after 15m0s. ~~~ _**East:**_ ~~~ shell -skupper link create ~/secret.token +skupper token redeem ~/token.yaml ~~~ _Sample output:_ ~~~ console -$ skupper link create ~/secret.token -Site configured to link to https://10.105.193.154:8081/ed9c37f6-d78a-11ec-a8c7-04421a4c5042 (name=link1) -Check the status of the link using 'skupper link status'. +$ skupper token redeem ~/token.yaml +Waiting for token status ... +Token "west-cad4f72d-2917-49b9-ab66-cdaca4d6cf9c" has been redeemed +You can now safely delete /run/user/1000/skewer/token.yaml ~~~ If your terminal sessions are on different machines, you may need @@ -237,108 +274,96 @@ to use `scp` or a similar tool to transfer the token securely. By default, tokens expire after a single use or 15 minutes after creation. -## Step 6: Fail on demand +## Step 7: Fail on demand _**West:**_ ~~~ shell if [ -n "${SKEWER_FAIL}" ]; then expr 1 / 0; fi +~~~ + +## Step 8: Fail expectedly + +_**West:**_ +~~~ shell +expr 1 / 0 ~~~ -## Step 7: Expose the backend +## Step 9: Expose the backend We now have our sites linked to form a Skupper network, but no -services are exposed on it. Skupper uses the `skupper expose` -command to select a service from one site for exposure in all the -linked sites. +services are exposed on it. -Use `skupper expose` to expose the backend service in East to -the frontend in West. +Skupper uses _listeners_ and _connectors_ to expose services. A +listener is a local endpoint for client connections, configured +with a routing key. A connector exists in a remote site and binds +a routing key to a particular set of servers. Skupper routers +forward client connections from local listeners to remote +connectors with matching routing keys. -_**East:**_ +In West, use the `skupper listener create` command to create a +listener for the backend. In East, use the `skupper connector +create` command to create a matching connector. + +_**West:**_ ~~~ shell -skupper expose deployment/backend --port 8080 +skupper listener create backend 8080 ~~~ _Sample output:_ ~~~ console -$ skupper expose deployment/backend --port 8080 -deployment backend exposed as backend +$ skupper listener create backend 8080 +Waiting for create to complete... +Listener "backend" is ready ~~~ -## Step 8: Access the frontend - -In order to use and test the application, we need external access -to the frontend. - -Use `kubectl expose` with `--type LoadBalancer` to open network -access to the frontend service. - -Once the frontend is exposed, use `kubectl get service/frontend` -to look up the external IP of the frontend service. If the -external IP is ``, try again after a moment. - -Once you have the external IP, use `curl` or a similar tool to -request the `/api/health` endpoint at that address. - -**Note:** The `` field in the following commands is a -placeholder. The actual value is an IP address. - -_**West:**_ +_**East:**_ ~~~ shell -kubectl expose deployment/frontend --port 8080 --type LoadBalancer -kubectl get service/frontend -curl http://:8080/api/health +skupper connector create backend 8080 ~~~ _Sample output:_ ~~~ console -$ kubectl expose deployment/frontend --port 8080 --type LoadBalancer -service/frontend exposed - -$ kubectl get service/frontend -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -frontend LoadBalancer 10.103.232.28 8080:30407/TCP 15s - -$ curl http://:8080/api/health -OK +$ skupper connector create backend 8080 +Waiting for create to complete... +Connector "backend" is ready ~~~ -If everything is in order, you can now access the web interface by -navigating to `http://:8080/` in your browser. +The commands shown above use the name argument, `backend`, to set +the default routing key and pod selector. You can use the +`--routing-key` and `--selector` options to specify other values. + +## Step 10: Access the frontend -## Cleaning up +In order to use and test the application, we need external access +to the frontend. -To remove Skupper and the other resources from this exercise, use -the following commands: +Use `kubectl port-forward` to make the frontend available at +`localhost:8080`. _**West:**_ ~~~ shell -skupper delete -kubectl delete service/frontend -kubectl delete deployment/frontend +kubectl port-forward deployment/frontend 8080:8080 ~~~ -_**East:**_ - -~~~ shell -skupper delete -kubectl delete deployment/backend -~~~ +You can now access the web interface by navigating to +[http://localhost:8080](http://localhost:8080) in your browser. ## Summary -A summary +More summary ## Next steps -Some next steps +Check out the other [examples][examples] on the Skupper website. + +More steps ## About this example diff --git a/external/skewer/example/skewer.yaml b/external/skewer/example/skewer.yaml index afc0b6b..bbbe8aa 100644 --- a/external/skewer/example/skewer.yaml +++ b/external/skewer/example/skewer.yaml @@ -18,20 +18,33 @@ sites: env: KUBECONFIG: ~/.kube/config-east steps: - - standard: install_the_skupper_command_line_tool - - standard: kubernetes/set_up_your_namespaces + - standard: kubernetes/set_up_your_clusters - standard: hello_world/deploy_the_frontend_and_backend + - standard: kubernetes/install_skupper_on_your_clusters + - standard: general/install_the_skupper_command_line_tool - standard: kubernetes/create_your_sites - - standard: link_your_sites + - standard: kubernetes/link_your_sites - title: Fail on demand commands: west: - - run: | - if [ -n "${SKEWER_FAIL}" ]; then expr 1 / 0; fi + - run: "if [ -n \"${SKEWER_FAIL}\" ]; then expr 1 / 0; fi" + - title: Fail expectedly + commands: + west: + - run: "expr 1 / 0" + expect_failure: true - standard: hello_world/expose_the_backend - standard: hello_world/access_the_frontend - - standard: hello_world/cleaning_up + # - standard: hello_world/cleaning_up + # preamble: | + # @default@ + + # And more! summary: | - A summary + @default@ + + More summary next_steps: | - Some next steps + @default@ + + More steps diff --git a/external/skewer/external/plano/src/plano/_tests.py b/external/skewer/external/plano/src/plano/_tests.py index cf67b89..dd91212 100644 --- a/external/skewer/external/plano/src/plano/_tests.py +++ b/external/skewer/external/plano/src/plano/_tests.py @@ -143,6 +143,10 @@ def dir_operations(): result = list_dir() assert result == [], result + print_dir() + print_dir(test_dir) + print_dir(test_dir, "*.not-there") + result = find(test_dir) assert result == [test_file_1, test_file_2], (result, [test_file_1, test_file_2]) @@ -314,6 +318,30 @@ def file_operations(): result = get_file_size(file) assert result == 10, result + zeta_dir = make_dir("zeta-dir") + zeta_file = touch(join(zeta_dir, "zeta-file")) + + eta_dir = make_dir("eta-dir") + eta_file = touch(join(eta_dir, "eta-file")) + + replace(zeta_dir, eta_dir) + assert not exists(zeta_file) + assert exists(zeta_dir) + assert is_file(join(zeta_dir, "eta-file")) + + with expect_exception(): + replace(zeta_dir, "not-there") + + assert exists(zeta_dir) + assert is_file(join(zeta_dir, "eta-file")) + + theta_file = write("theta-file", "theta") + iota_file = write("iota-file", "iota") + + replace(theta_file, iota_file) + assert not exists(iota_file) + assert read(theta_file) == "iota" + @test def github_operations(): result = convert_github_markdown("# Hello, Fritz") @@ -479,7 +507,7 @@ def io_operations(): assert is_file(file_c), file_c file_d = write("d", "front@middle@@middle@back") - path = replace_in_file(file_d, "@middle@", "M", count=1) + path = string_replace_file(file_d, "@middle@", "M", count=1) result = read(path) assert result == "frontM@middle@back", result @@ -837,10 +865,10 @@ def process_operations(): @test def string_operations(): - result = replace("ab", "a", "b") + result = string_replace("ab", "a", "b") assert result == "bb", result - result = replace("aba", "a", "b", count=1) + result = string_replace("aba", "a", "b", count=1) assert result == "bba", result result = remove_prefix(None, "xxx") diff --git a/external/skewer/external/plano/src/plano/github.py b/external/skewer/external/plano/src/plano/github.py index 8bfd9f8..e1714b5 100644 --- a/external/skewer/external/plano/src/plano/github.py +++ b/external/skewer/external/plano/src/plano/github.py @@ -56,7 +56,7 @@ def convert_github_markdown(markdown): content = http_post("https://api.github.com/markdown", json, content_type="application/json") # Remove the "user-content-" prefix from internal anchors - content = replace(content, "id=\"user-content-", "id=\"") + content = content.replace("id=\"user-content-", "id=\"") return _html_template.replace("@content@", content) @@ -67,6 +67,8 @@ def update_external_from_github(dir, owner, repo, ref="main"): url = f"https://github.com/{owner}/{repo}/archive/{ref}.tar.gz" with temp_file() as temp: + assert exists(temp) + http_get(url, output_file=temp) with working_dir(quiet=True): @@ -75,4 +77,4 @@ def update_external_from_github(dir, owner, repo, ref="main"): extracted_dir = list_dir()[0] assert is_dir(extracted_dir) - move(extracted_dir, dir, inside=False) + replace(dir, extracted_dir) diff --git a/external/skewer/external/plano/src/plano/main.py b/external/skewer/external/plano/src/plano/main.py index e0fb31c..9a99fb1 100644 --- a/external/skewer/external/plano/src/plano/main.py +++ b/external/skewer/external/plano/src/plano/main.py @@ -252,18 +252,18 @@ def print_properties(props, file=None): ## Directory operations -def find(dirs=None, include="*", exclude=()): +def find(dirs=None, include="*", exclude=[]): if dirs is None: dirs = "." if is_string(dirs): - dirs = (dirs,) + dirs = [dirs] if is_string(include): - include = (include,) + include = [include] if is_string(exclude): - exclude = (exclude,) + exclude = [exclude] found = set() @@ -313,7 +313,7 @@ def change_dir(dir, quiet=False): return prev_dir -def list_dir(dir=None, include="*", exclude=()): +def list_dir(dir=None, include="*", exclude=[]): if dir is None: dir = get_current_dir() else: @@ -322,10 +322,10 @@ def list_dir(dir=None, include="*", exclude=()): assert is_dir(dir), dir if is_string(include): - include = (include,) + include = [include] if is_string(exclude): - exclude = (exclude,) + exclude = [exclude] names = _os.listdir(dir) @@ -338,6 +338,22 @@ def list_dir(dir=None, include="*", exclude=()): return sorted(names) +def print_dir(dir=None, include="*", exclude=[]): + if dir is None: + dir = get_current_dir() + else: + dir = expand(dir) + + names = list_dir(dir=dir, include=include, exclude=exclude) + + print("{}:".format(get_absolute_path(dir))) + + if names: + for name in names: + print(f" {name}") + else: + print(" [none]") + # No args constructor gets a temp dir class working_dir: def __init__(self, dir=None, quiet=False): @@ -519,13 +535,13 @@ def copy(from_path, to_path, symlinks=True, inside=True, quiet=False): else: make_parent_dir(to_path, quiet=True) - if is_dir(from_path): + if is_link(from_path) and symlinks: + make_link(to_path, read_link(from_path), quiet=True) + elif is_dir(from_path): for name in list_dir(from_path): copy(join(from_path, name), join(to_path, name), symlinks=symlinks, inside=False, quiet=True) _shutil.copystat(from_path, to_path) - elif is_link(from_path) and symlinks: - make_link(to_path, read_link(from_path), quiet=True) else: _shutil.copy2(from_path, to_path) @@ -543,9 +559,39 @@ def move(from_path, to_path, inside=True, quiet=False): return to_path +def replace(path, replacement, quiet=False): + path = expand(path) + replacement = expand(replacement) + + _notice(quiet, "Replacing {} with {}", repr(path), repr(replacement)) + + with temp_dir() as backup_dir: + backup = join(backup_dir, "backup") + backup_created = False + + if exists(path): + move(path, backup, quiet=True) + backup_created = True + + try: + move(replacement, path, quiet=True) + except OSError: + notice("Removing") + remove(path, quiet=True) + + if backup_created: + move(backup, path, quiet=True) + + raise + + assert not exists(replacement), replacement + assert exists(path), path + + return path + def remove(paths, quiet=False): if is_string(paths): - paths = (paths,) + paths = [paths] for path in paths: path = expand(path) @@ -649,9 +695,9 @@ def tail_lines(file, count): return lines[-count:] -def replace_in_file(file, expr, replacement, count=0): +def string_replace_file(file, expr, replacement, count=0): file = expand(file) - return write(file, replace(read(file), expr, replacement, count=count)) + return write(file, string_replace(read(file), expr, replacement, count=count)) def concatenate(file, input_files): file = expand(file) @@ -677,7 +723,7 @@ def unique(iterable): def skip(iterable, values=(None, "", (), [], {})): if is_scalar(values): - values = (values,) + values = [values] items = list() @@ -1387,7 +1433,7 @@ def _default_sigterm_handler(signum, frame): ## String operations -def replace(string, expr, replacement, count=0): +def string_replace(string, expr, replacement, count=0): return _re.sub(expr, replacement, string, count) def remove_prefix(string, prefix): diff --git a/external/skewer/python/skewer/main.py b/external/skewer/python/skewer/main.py index b9aed11..297bf91 100644 --- a/external/skewer/python/skewer/main.py +++ b/external/skewer/python/skewer/main.py @@ -37,10 +37,10 @@ def check_environment(): def resource_exists(resource): return run(f"kubectl get {resource}", output=DEVNULL, check=False, quiet=True).exit_code == 0 -def get_resource_jsonpath(resource, jsonpath): +def get_resource_json(resource, jsonpath=""): return call(f"kubectl get {resource} -o jsonpath='{{{jsonpath}}}'", quiet=True) -def await_resource(resource, timeout=240): +def await_resource(resource, timeout=300): assert "/" in resource, resource start_time = get_time() @@ -63,7 +63,7 @@ def await_resource(resource, timeout=240): run(f"kubectl logs {resource}") raise -def await_external_ip(service, timeout=240): +def await_ingress(service, timeout=300): assert service.startswith("service/"), service start_time = get_time() @@ -71,24 +71,36 @@ def await_external_ip(service, timeout=240): await_resource(service, timeout=timeout) while True: - notice(f"Waiting for external IP from {service} to become available") + notice(f"Waiting for hostname or IP from {service} to become available") - if get_resource_jsonpath(service, ".status.loadBalancer.ingress") != "": + json = get_resource_json(service, ".status.loadBalancer.ingress") + + if json != "": break if get_time() - start_time > timeout: - fail(f"Timed out waiting for external IP for {service}") + fail(f"Timed out waiting for hostnmae or external IP for {service}") sleep(5, quiet=True) - return get_resource_jsonpath(service, ".status.loadBalancer.ingress[0].ip") + data = parse_json(json) + + if len(data): + if "hostname" in data[0]: + return data[0]["hostname"] + + if "ip" in data[0]: + return data[0]["ip"] -def await_http_ok(service, url_template, user=None, password=None, timeout=240): + fail(f"Failed to get hostname or IP from {service}") + +def await_http_ok(service, url_template, user=None, password=None, timeout=300): assert service.startswith("service/"), service start_time = get_time() - ip = await_external_ip(service, timeout=timeout) + ip = await_ingress(service, timeout=timeout) + url = url_template.format(ip) insecure = url.startswith("https") @@ -108,7 +120,7 @@ def await_http_ok(service, url_template, user=None, password=None, timeout=240): def await_console_ok(): await_resource("secret/skupper-console-users") - password = get_resource_jsonpath("secret/skupper-console-users", ".data.admin") + password = get_resource_json("secret/skupper-console-users", ".data.admin") password = base64_decode(password) await_http_ok("service/skupper", "https://{}:8010/", user="admin", password=password) @@ -164,8 +176,8 @@ def run_step(model, step, work_dir, check=True): if command.await_resource: await_resource(command.await_resource) - if command.await_external_ip: - await_external_ip(command.await_external_ip) + if command.await_ingress: + await_ingress(command.await_ingress) if command.await_http_ok: await_http_ok(*command.await_http_ok) @@ -173,8 +185,20 @@ def run_step(model, step, work_dir, check=True): if command.await_console_ok: await_console_ok() + if command.await_port: + await_port(command.await_port, timeout=300) + if command.run: - run(command.run.replace("~", work_dir), shell=True, check=check) + proc = run(command.run.replace("~", work_dir), shell=True, check=False) + + if command.expect_failure: + if proc.exit_code == 0: + fail("A command expected to fail did not fail") + + continue + + if check and proc.exit_code > 0: + raise PlanoProcessError(proc) def pause_for_demo(model): notice("Pausing for demo time") @@ -186,17 +210,15 @@ def pause_for_demo(model): if first_site.platform == "kubernetes": with first_site: - if resource_exists("service/frontend"): - if get_resource_jsonpath("service/frontend", ".spec.type") == "LoadBalancer": - frontend_ip = await_external_ip("service/frontend") - frontend_url = f"http://{frontend_ip}:8080/" + if resource_exists("deployment/frontend"): + frontend_url = f"http://localhost:8080/" if resource_exists("secret/skupper-console-users"): - console_ip = await_external_ip("service/skupper") - console_url = f"https://{console_ip}:8010/" + console_host = await_ingress("service/skupper") + console_url = f"https://{console_host}:8010/" await_resource("secret/skupper-console-users") - password = get_resource_jsonpath("secret/skupper-console-users", ".data.admin") + password = get_resource_json("secret/skupper-console-users", ".data.admin") password = base64_decode(password).decode("ascii") print() @@ -284,9 +306,9 @@ def append_toc_entry(heading, condition=True): if not condition: return - fragment = replace(heading, r"[ -]", "_") - fragment = replace(fragment, r"[\W]", "") - fragment = replace(fragment, "_", "-") + fragment = string_replace(heading, r"[ -]", "_") + fragment = string_replace(fragment, r"[\W]", "") + fragment = string_replace(fragment, "_", "-") fragment = fragment.lower() out.append(f"* [{heading}](#{fragment})") @@ -297,9 +319,12 @@ def append_section(heading, text): out.append(f"## {heading}") out.append("") - out.append(text.strip()) + out.append(text) out.append("") + out.append("") + out.append("") + out.append(f"# {model.title}") out.append("") @@ -318,14 +343,14 @@ def append_section(heading, text): out.append("") append_toc_entry("Overview", model.overview) - append_toc_entry("Prerequisites") + append_toc_entry("Prerequisites", model.prerequisites) for step in model.steps: append_toc_entry(generate_step_heading(step)) - append_toc_entry("Summary") - append_toc_entry("Next steps") - append_toc_entry("About this example") + append_toc_entry("Summary", model.summary) + append_toc_entry("Next steps", model.next_steps) + append_toc_entry("About this example", model.about_this_example) out.append("") @@ -340,7 +365,7 @@ def append_section(heading, text): append_section("Summary", model.summary) append_section("Next steps", model.next_steps) - append_section("About this example", standard_text["about_this_example"].strip()) + append_section("About this example", model.about_this_example) write(output_file, "\n".join(out).strip() + "\n") @@ -415,17 +440,22 @@ def apply_standard_steps(model): del step.data["standard"] def apply_attribute(name, default=None): - if name not in step.data: - value = standard_step_data.get(name, default) + standard_value = standard_step_data.get(name, default) + value = step.data.get(name, standard_value) + + if is_string(value): + if standard_value is not None: + value = value.replace("@default@", str(nvl(standard_value, "")).strip()) + + for i, site in enumerate([x for _, x in model.sites]): + value = value.replace(f"@site{i}@", site.title) - if value and name in ("title", "preamble", "postamble"): - for i, site in enumerate([x for _, x in model.sites]): - value = value.replace(f"@site{i}@", site.title) + if site.namespace: + value = value.replace(f"@namespace{i}@", site.namespace) - if site.namespace: - value = value.replace(f"@namespace{i}@", site.namespace) + value = value.strip() - step.data[name] = value + step.data[name] = value apply_attribute("name") apply_attribute("title") @@ -501,7 +531,13 @@ def get_github_owner_repo(): def object_property(name, default=None): def get(obj): - return obj.data.get(name, default) + value = obj.data.get(name, default) + + if is_string(value): + value = value.replace("@default@", str(nvl(default, "")).strip()) + value = value.strip() + + return value return property(get) @@ -522,9 +558,10 @@ class Model: subtitle = object_property("subtitle") workflow = object_property("workflow", "main.yaml") overview = object_property("overview") - prerequisites = object_property("prerequisites", standard_text["prerequisites"].strip()) + prerequisites = object_property("prerequisites", standard_text["prerequisites"]) summary = object_property("summary") - next_steps = object_property("next_steps", standard_text["next_steps"].strip()) + next_steps = object_property("next_steps", standard_text["next_steps"]) + about_this_example = object_property("about_this_example", standard_text["about_this_example"]) def __init__(self, skewer_file, kubeconfigs=[]): self.skewer_file = skewer_file @@ -588,7 +625,7 @@ def check(self): check_required_attributes(self, "platform") check_unknown_attributes(self) - if self.platform not in ("kubernetes", "podman"): + if self.platform not in ("kubernetes", "podman", None): fail(f"{self} attribute 'platform' has an illegal value: {self.platform}") if self.platform == "kubernetes": @@ -648,12 +685,14 @@ def commands(self): class Command: run = object_property("run") + expect_failure = object_property("expect_failure", False) apply = object_property("apply") output = object_property("output") await_resource = object_property("await_resource") - await_external_ip = object_property("await_external_ip") + await_ingress = object_property("await_ingress") await_http_ok = object_property("await_http_ok") await_console_ok = object_property("await_console_ok") + await_port = object_property("await_port") def __init__(self, model, data): self.model = model @@ -691,23 +730,34 @@ def __enter__(self): run("minikube start -p skewer --auto-update-drivers false") - tunnel_output_file = open(f"{self.work_dir}/minikube-tunnel-output", "w") - self.tunnel = start("minikube tunnel -p skewer", output=tunnel_output_file) + try: + tunnel_output_file = open(f"{self.work_dir}/minikube-tunnel-output", "w") + self.tunnel = start("minikube tunnel -p skewer", output=tunnel_output_file) + + try: + model = Model(self.skewer_file) + model.check() - model = Model(self.skewer_file) - model.check() + kube_sites = [x for _, x in model.sites if x.platform == "kubernetes"] - kube_sites = [x for _, x in model.sites if x.platform == "kubernetes"] + for site in kube_sites: + kubeconfig = site.env["KUBECONFIG"] + kubeconfig = kubeconfig.replace("~", self.work_dir) + kubeconfig = expand(kubeconfig) - for site in kube_sites: - kubeconfig = site.env["KUBECONFIG"].replace("~", self.work_dir) - site.env["KUBECONFIG"] = kubeconfig + site.env["KUBECONFIG"] = kubeconfig - self.kubeconfigs.append(kubeconfig) + self.kubeconfigs.append(kubeconfig) - with site: - run("minikube update-context -p skewer") - check_file(ENV["KUBECONFIG"]) + with site: + run("minikube update-context -p skewer") + check_file(ENV["KUBECONFIG"]) + except: + stop(self.tunnel) + raise + except: + run("minikube delete -p skewer") + raise return self diff --git a/external/skewer/python/skewer/planocommands.py b/external/skewer/python/skewer/planocommands.py index 754fb1e..5a718b3 100644 --- a/external/skewer/python/skewer/planocommands.py +++ b/external/skewer/python/skewer/planocommands.py @@ -87,5 +87,5 @@ def update_skewer(): This results in local changes to review and commit. """ - update_external_from_github("external/skewer", "skupperproject", "skewer") + update_external_from_github("external/skewer", "skupperproject", "skewer", "v2") copy("external/skewer/config/.github/workflows/main.yaml", ".github/workflows/main.yaml") diff --git a/external/skewer/python/skewer/standardsteps.yaml b/external/skewer/python/skewer/standardsteps.yaml index ca128c0..3fc2b13 100644 --- a/external/skewer/python/skewer/standardsteps.yaml +++ b/external/skewer/python/skewer/standardsteps.yaml @@ -17,18 +17,18 @@ # under the License. # -install_the_skupper_command_line_tool: +general/install_the_skupper_command_line_tool: title: Install the Skupper command-line tool preamble: | - This example uses the Skupper command-line tool to deploy Skupper. - You need to install the `skupper` command only once for each - development environment. + This example uses the Skupper command-line tool to create Skupper + resources. You need to install the `skupper` command only once + for each development environment. On Linux or Mac, you can use the install script (inspect it [here][install-script]) to download and extract the command: ~~~ shell - curl https://skupper.io/install.sh | sh + curl https://skupper.io/install.sh | sh -s -- --version 2.0.0-preview-1 ~~~ The script installs the command under your home directory. It @@ -39,14 +39,63 @@ install_the_skupper_command_line_tool: [install-script]: https://github.com/skupperproject/skupper-website/blob/main/input/install.sh [install-docs]: https://skupper.io/install/ -kubernetes/set_up_your_namespaces: - title: Set up your namespaces +# general/link_your_sites: +# title: Link your sites +# preamble: | +# A Skupper _link_ is a channel for communication between two sites. +# Links serve as a transport for application connections and +# requests. + +# Creating a link requires use of two `skupper` commands in +# conjunction, `skupper token create` and `skupper link create`. + +# The `skupper token create` command generates a secret token that +# signifies permission to create a link. The token also carries the +# link details. Then, in a remote site, The `skupper link +# create` command uses the token to create a link to the site +# that generated it. + +# **Note:** The link token is truly a *secret*. Anyone who has the +# token can link to your site. Make sure that only those you trust +# have access to it. + +# First, use `skupper token create` in @site0@ to generate the +# token. Then, use `skupper link create` in @site1@ to link the +# sites. +# commands: +# "0": +# - run: skupper token create ~/secret.token +# output: Token written to ~/secret.token +# "1": +# - run: skupper link create ~/secret.token +# output: | +# Site configured to link to (name=link1) +# Check the status of the link using 'skupper link status'. +# - run: skupper link status --wait 60 +# apply: test +# postamble: | +# If your terminal sessions are on different machines, you may need +# to use `scp` or a similar tool to transfer the token securely. By +# default, tokens expire after a single use or 15 minutes after +# creation. +general/cleaning_up: + name: cleaning_up + title: Cleaning up + numbered: false + preamble: | + To remove Skupper and the other resources from this exercise, use + the following commands. + commands: + "*": + - run: skupper delete +kubernetes/set_up_your_clusters: + title: Set up your clusters platform: kubernetes preamble: | - Skupper is designed for use with multiple Kubernetes namespaces, - usually on different clusters. The `skupper` and `kubectl` - commands use your [kubeconfig][kubeconfig] and current context to - select the namespace where they operate. + Skupper is designed for use with multiple Kubernetes clusters. + The `skupper` and `kubectl` commands use your + [kubeconfig][kubeconfig] and current context to select the cluster + and namespace where they operate. [kubeconfig]: https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/ @@ -77,9 +126,12 @@ kubernetes/set_up_your_namespaces: - run: export KUBECONFIG=@kubeconfig@ - run: "# Enter your provider-specific login command" - run: kubectl create namespace @namespace@ + apply: readme + - run: kubectl create namespace @namespace@ --dry-run=client -o yaml | kubectl apply -f - + apply: test - run: kubectl config set-context --current --namespace @namespace@ -kubernetes/set_up_your_kubernetes_namespace: - title: Set up your Kubernetes namespace +kubernetes/set_up_your_kubernetes_cluster: + title: Set up your Kubernetes cluster platform: kubernetes preamble: | Open a new terminal window and log in to your cluster. Then @@ -100,6 +152,19 @@ kubernetes/set_up_your_kubernetes_namespace: - run: "# Enter your provider-specific login command" - run: kubectl create namespace @namespace@ - run: kubectl config set-context --current --namespace @namespace@ +kubernetes/install_skupper_on_your_clusters: + title: Install Skupper on your clusters + platform: kubernetes + preamble: | + Using Skupper on Kubernetes requires the installation of the + Skupper custom resource definitions (CRDs) and the Skupper + controller. + + For each cluster, use `kubectl apply` with the Skupper + installation YAML to install the CRDs and controller. + commands: + "*": + - run: kubectl apply -f https://skupper.io/v2/install.yaml kubernetes/create_your_sites: title: Create your sites platform: kubernetes @@ -109,99 +174,109 @@ kubernetes/create_your_sites: network for your application. In Kubernetes, a site is associated with a namespace. - For each namespace, use `skupper init` to create a site. This - deploys the Skupper router and controller. Then use `skupper - status` to see the outcome. + For each namespace, use `skupper site create` with a site name of + your choice. This creates the site resource and deploys the + Skupper router to the namespace. **Note:** If you are using Minikube, you need to [start minikube tunnel][minikube-tunnel] before you run `skupper init`. + + [minikube-tunnel]: https://skupper.io/start/minikube.html#running-minikube-tunnel commands: "0": - - run: skupper init + - run: skupper site create @namespace@ --enable-link-access output: | - Waiting for LoadBalancer IP or hostname... Waiting for status... - Skupper is now installed in namespace 'west'. Use 'skupper status' to get more information. - - run: skupper status + Site "@namespace@" is configured. Check the status to see when it is ready + - run: | + kubectl wait --for=condition=Ready site/@namespace@ # Required with preview 1 - to be removed! output: | - Skupper is enabled for namespace "west". It is not connected to any other sites. It has no exposed services. + site.skupper.io/@namespace@ condition met "*": - - run: skupper init + - run: skupper site create @namespace@ output: | - Waiting for LoadBalancer IP or hostname... Waiting for status... - Skupper is now installed in namespace 'east'. Use 'skupper status' to get more information. - - run: skupper status + Site "@namespace@" is configured. Check the status to see when it is ready + - run: | + kubectl wait --for=condition=Ready site/@namespace@ # Required with preview 1 - to be removed! output: | - Skupper is enabled for namespace "east". It is not connected to any other sites. It has no exposed services. + site.skupper.io/@namespace@ condition met postamble: | - As you move through the steps below, you can use `skupper status` at - any time to check your progress. -podman/set_up_your_podman_network: - title: Set up your Podman network - platform: podman - preamble: | - Open a new terminal window and set the `SKUPPER_PLATFORM` - environment variable to `podman`. This sets the Skupper platform - to Podman for this terminal session. - - Use `podman network create` to create the Podman network that - Skupper will use. - - Use `systemctl` to enable the Podman API service. - commands: - "*": - - run: export SKUPPER_PLATFORM=podman - - run: podman network create skupper - apply: readme - - run: if ! podman network exists skupper; then podman network create skupper; fi - apply: test - - run: systemctl --user enable --now podman.socket - postamble: | - If the `systemctl` command doesn't work, you can try the `podman - system service` command instead: - - ~~~ - podman system service --time=0 unix://$XDG_RUNTIME_DIR/podman/podman.sock & - ~~~ -link_your_sites: + You can use `skupper site status` at any time to check the status + of your site. +kubernetes/link_your_sites: title: Link your sites + platform: kubernetes preamble: | - Creating a link requires use of two `skupper` commands in - conjunction, `skupper token create` and `skupper link create`. + A Skupper _link_ is a channel for communication between two sites. + Links serve as a transport for application connections and + requests. + + Creating a link requires use of two Skupper commands in + conjunction, `skupper token issue` and `skupper token redeem`. - The `skupper token create` command generates a secret token that + The `skupper token issue` command generates a secret token that signifies permission to create a link. The token also carries the - link details. Then, in a remote site, The `skupper link - create` command uses the token to create a link to the site - that generated it. + link details. Then, in a remote site, The `skupper token redeem` + command uses the token to create a link to the site that generated + it. **Note:** The link token is truly a *secret*. Anyone who has the token can link to your site. Make sure that only those you trust have access to it. - First, use `skupper token create` in site @site0@ to generate the - token. Then, use `skupper link create` in site @site1@ to link - the sites. + First, use `skupper token issue` in @site0@ to generate the token. + Then, use `skupper token redeem` in @site1@ to link the sites. commands: "0": - - run: skupper token create ~/secret.token - output: Token written to ~/secret.token + - run: skupper token issue ~/token.yaml + output: | + Waiting for token status ... + + Grant "west-cad4f72d-2917-49b9-ab66-cdaca4d6cf9c" is ready + Token file /run/user/1000/skewer/token.yaml created + + Transfer this file to a remote site. At the remote site, + create a link to this site using the "skupper token redeem" command: + + skupper token redeem + + The token expires after 1 use(s) or after 15m0s. "1": - - run: skupper link create ~/secret.token + - run: skupper token redeem ~/token.yaml output: | - Site configured to link to https://10.105.193.154:8081/ed9c37f6-d78a-11ec-a8c7-04421a4c5042 (name=link1) - Check the status of the link using 'skupper link status'. - - run: skupper link status --wait 60 - apply: test + Waiting for token status ... + Token "west-cad4f72d-2917-49b9-ab66-cdaca4d6cf9c" has been redeemed + You can now safely delete /run/user/1000/skewer/token.yaml postamble: | If your terminal sessions are on different machines, you may need to use `scp` or a similar tool to transfer the token securely. By default, tokens expire after a single use or 15 minutes after creation. -cleaning_up: +kubernetes/access_the_frontend: + title: Access the frontend + preamble: | + In order to use and test the application, we need external access + to the frontend. + + Use `kubectl port-forward` to make the frontend available at + `localhost:8080`. + commands: + "0": + - await_resource: deployment/frontend + - run: kubectl port-forward deployment/frontend 8080:8080 + apply: readme + - run: kubectl port-forward deployment/frontend 8080:8080 > /dev/null & + apply: test + - await_port: 8080 + - run: curl http://localhost:8080/api/health + apply: test + postamble: | + You can now access the web interface by navigating to + [http://localhost:8080](http://localhost:8080) in your browser. +kubernetes/cleaning_up: name: cleaning_up title: Cleaning up numbered: false @@ -211,15 +286,41 @@ cleaning_up: commands: "*": - run: skupper delete +podman/set_up_your_podman_environment: + title: Set up your Podman environment + platform: podman + preamble: | + Open a new terminal window and set the `SKUPPER_PLATFORM` + environment variable to `podman`. This sets the Skupper platform + to Podman for this terminal session. + + Use `podman network create` to create the Podman network that + Skupper will use. + + Use `systemctl` to enable the Podman API service. + commands: + "*": + - run: export SKUPPER_PLATFORM=podman + - run: podman network create skupper + apply: readme + - run: if ! podman network exists skupper; then podman network create skupper; fi + apply: test + - run: systemctl --user enable --now podman.socket + postamble: | + If the `systemctl` command doesn't work, you can try the `podman + system service` command instead: + + ~~~ + podman system service --time=0 unix://$XDG_RUNTIME_DIR/podman/podman.sock & + ~~~ hello_world/deploy_the_frontend_and_backend: - title: Deploy the frontent and backend + title: Deploy the frontend and backend preamble: | This example runs the frontend and the backend in separate Kubernetes namespaces, on different clusters. - Use `kubectl create deployment` to deploy the frontend in - namespace `@namespace0@` and the backend in namespace - `@namespace1@`. + Use `kubectl create deployment` to deploy the frontend in @site0@ + and the backend in @site1@. commands: "0": - run: kubectl create deployment frontend --image quay.io/skupper/hello-world-frontend @@ -229,52 +330,54 @@ hello_world/expose_the_backend: title: Expose the backend preamble: | We now have our sites linked to form a Skupper network, but no - services are exposed on it. Skupper uses the `skupper expose` - command to select a service from one site for exposure in all the - linked sites. + services are exposed on it. - Use `skupper expose` to expose the backend service in @site1@ to - the frontend in @site0@. + Skupper uses _listeners_ and _connectors_ to expose services. A + listener is a local endpoint for client connections, configured + with a routing key. A connector exists in a remote site and binds + a routing key to a particular set of servers. Skupper routers + forward client connections from local listeners to remote + connectors with matching routing keys. + + In @site0@, use the `skupper listener create` command to create a + listener for the backend. In @site1@, use the `skupper connector + create` command to create a matching connector. commands: + "0": + - run: skupper listener create backend 8080 + output: | + Waiting for create to complete... + Listener "backend" is ready "1": - - await_resource: deployment/backend - - run: skupper expose deployment/backend --port 8080 - output: deployment backend exposed as backend + - run: skupper connector create backend 8080 + output: | + Waiting for create to complete... + Connector "backend" is ready + postamble: | + The commands shown above use the name argument, `backend`, to set + the default routing key and pod selector. You can use the + `--routing-key` and `--selector` options to specify other values. hello_world/access_the_frontend: title: Access the frontend preamble: | In order to use and test the application, we need external access to the frontend. - Use `kubectl expose` with `--type LoadBalancer` to open network - access to the frontend service. - - Once the frontend is exposed, use `kubectl get service/frontend` - to look up the external IP of the frontend service. If the - external IP is ``, try again after a moment. - - Once you have the external IP, use `curl` or a similar tool to - request the `/api/health` endpoint at that address. - - **Note:** The `` field in the following commands is a - placeholder. The actual value is an IP address. + Use `kubectl port-forward` to make the frontend available at + `localhost:8080`. commands: "0": - - run: kubectl expose deployment/frontend --port 8080 --type LoadBalancer - output: service/frontend exposed - - await_resource: service/frontend - - run: kubectl get service/frontend - apply: readme - output: | - NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE - frontend LoadBalancer 10.103.232.28 8080:30407/TCP 15s - - run: curl http://:8080/api/health + - await_resource: deployment/frontend + - run: kubectl port-forward deployment/frontend 8080:8080 apply: readme - output: OK - - await_http_ok: [service/frontend, "http://{}:8080/api/health"] + - run: kubectl port-forward deployment/frontend 8080:8080 > /dev/null & + apply: test + - await_port: 8080 + - run: curl http://localhost:8080/api/health + apply: test postamble: | - If everything is in order, you can now access the web interface by - navigating to `http://:8080/` in your browser. + You can now access the web interface by navigating to + [http://localhost:8080](http://localhost:8080) in your browser. hello_world/cleaning_up: name: cleaning_up title: Cleaning up @@ -285,7 +388,6 @@ hello_world/cleaning_up: commands: "0": - run: skupper delete - - run: kubectl delete service/frontend - run: kubectl delete deployment/frontend "1": - run: skupper delete diff --git a/skewer.yaml b/skewer.yaml index 21607f5..5ff2137 100644 --- a/skewer.yaml +++ b/skewer.yaml @@ -39,28 +39,8 @@ sites: env: KUBECONFIG: ~/.kube/config-east steps: - - title: Install Skupper in your clusters - preamble: | - Use the `kubectl apply` command to install the Skupper - controller in each cluster. - commands: - west: - - run: kubectl apply -f skupper.yaml - output: | - namespace/skupper-site-controller created - serviceaccount/skupper-site-controller created - clusterrole.rbac.authorization.k8s.io/skupper-site-controller created - clusterrolebinding.rbac.authorization.k8s.io/skupper-site-controller created - deployment.apps/skupper-site-controller created - east: - - run: kubectl apply -f skupper.yaml - output: | - namespace/skupper-site-controller created - serviceaccount/skupper-site-controller created - clusterrole.rbac.authorization.k8s.io/skupper-site-controller created - clusterrolebinding.rbac.authorization.k8s.io/skupper-site-controller created - deployment.apps/skupper-site-controller created - - standard: kubernetes/set_up_your_namespaces + - standard: kubernetes/set_up_your_clusters + - standard: kubernetes/install_skupper_on_your_clusters - title: Apply your YAML resources preamble: | To configure our example sites and service bindings, we are @@ -90,12 +70,13 @@ steps: [site.yaml](west/site.yaml): ~~~ yaml - apiVersion: v1 - kind: ConfigMap + apiVersion: skupper.io/v1alpha1 + kind: Site metadata: - name: skupper-site - data: name: west + namespace: west + spec: + linkAccess: default ~~~ #### Resources in East @@ -107,13 +88,11 @@ steps: [site.yaml](east/site.yaml): ~~~ yaml - apiVersion: v1 - kind: ConfigMap + apiVersion: skupper.io/v1alpha1 + kind: Site metadata: - name: skupper-site - data: name: east - ingress: none + namespace: east ~~~ In East, the `backend` deployment has an annotation named @@ -158,20 +137,23 @@ steps: [minikube-tunnel]: https://skupper.io/start/minikube.html#running-minikube-tunnel commands: west: - - run: kubectl apply -f west/site.yaml -f west/frontend.yaml + - run: kubectl apply -f west/site.yaml -f west/frontend.yaml -f west/listener.yaml output: | + XXX configmap/skupper-site created deployment.apps/frontend created - - await_resource: deployment/skupper-service-controller + - run: kubectl wait site/west --for condition=Ready --timeout 120s - await_resource: deployment/frontend east: - - run: kubectl apply -f east/site.yaml -f east/backend.yaml + - run: kubectl apply -f east/site.yaml -f east/backend.yaml -f east/connector.yaml output: | + XXX configmap/skupper-site created deployment.apps/backend created - - await_resource: deployment/skupper-service-controller + # - await_resource: deployment/skupper-service-controller + - run: kubectl wait site/east --for condition=Ready --timeout 120s - await_resource: deployment/backend - - standard: link_your_sites + - standard: kubernetes/link_your_sites preamble: | You can configure sites and service bindings declaratively, but linking sites is different. To create a link, you must have the @@ -192,7 +174,7 @@ steps: To install the Skupper command: ~~~ shell - curl https://skupper.io/install.sh | sh + curl https://skupper.io/install.sh | sh -s -- --version 2.0.0-preview-1 ~~~ For more installation options, see [Installing @@ -204,14 +186,14 @@ steps: [install]: https://skupper.io/install/index.html - standard: hello_world/access_the_frontend - - standard: cleaning_up + - standard: kubernetes/cleaning_up commands: west: - - run: kubectl delete -f west/site.yaml -f west/frontend.yaml - - run: kubectl delete -f skupper.yaml + - run: kubectl delete -f west/site.yaml -f west/frontend.yaml -f west/listener.yaml + - run: kubectl delete -f https://skupper.io/v2/install.yaml east: - - run: kubectl delete -f east/site.yaml -f east/backend.yaml - - run: kubectl delete -f skupper.yaml + - run: kubectl delete -f east/site.yaml -f east/backend.yaml -f east/connector.yaml + - run: kubectl delete -f https://skupper.io/v2/install.yaml apply: readme - - run: kubectl delete -f skupper.yaml --ignore-not-found + - run: kubectl delete -f https://skupper.io/v2/install.yaml --ignore-not-found apply: test diff --git a/west/listener.yaml b/west/listener.yaml new file mode 100644 index 0000000..99dbac2 --- /dev/null +++ b/west/listener.yaml @@ -0,0 +1,9 @@ +apiVersion: skupper.io/v1alpha1 +kind: Listener +metadata: + name: backend + namespace: west +spec: + routingKey: backend + port: 8080 + host: backend diff --git a/west/site.yaml b/west/site.yaml index 87ed2bf..1e9b2a1 100644 --- a/west/site.yaml +++ b/west/site.yaml @@ -1,6 +1,7 @@ -apiVersion: v1 -kind: ConfigMap +apiVersion: skupper.io/v1alpha1 +kind: Site metadata: - name: skupper-site -data: name: west + namespace: west +spec: + linkAccess: default