From 7114108898080c9ec20ea3d4670b1a8a55d3385e Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Wed, 14 Apr 2021 20:48:42 -0600 Subject: [PATCH] Move deployment documentation to its own section Signed-off-by: Tim Hansen --- .../docker.md} | 19 +- .../deployment-helm.md => deployment/helm.md} | 8 +- docs/deployment/heroku.md | 44 ++ docs/deployment/index.md | 40 ++ docs/deployment/k8s.md | 519 ++++++++++++++++++ docs/getting-started/deployment-k8s.md | 13 - docs/getting-started/deployment-other.md | 35 -- microsite/sidebars.json | 16 +- 8 files changed, 623 insertions(+), 71 deletions(-) rename docs/{getting-started/deployment-docker.md => deployment/docker.md} (95%) rename docs/{getting-started/deployment-helm.md => deployment/helm.md} (92%) create mode 100644 docs/deployment/heroku.md create mode 100644 docs/deployment/index.md create mode 100644 docs/deployment/k8s.md delete mode 100644 docs/getting-started/deployment-k8s.md delete mode 100644 docs/getting-started/deployment-other.md diff --git a/docs/getting-started/deployment-docker.md b/docs/deployment/docker.md similarity index 95% rename from docs/getting-started/deployment-docker.md rename to docs/deployment/docker.md index 70963df3d3..2133919770 100644 --- a/docs/getting-started/deployment-docker.md +++ b/docs/deployment/docker.md @@ -1,14 +1,15 @@ --- -id: deployment-docker -title: Docker -description: Documentation on how to deploy Backstage as a Docker image +id: docker +title: Building a Docker image +sidebar_label: Docker +description: How to build a Backstage Docker image for deployment --- This section describes how to build a Backstage App into a deployable Docker image. It is split into three sections, first covering the host build approach, which is recommended due its speed and more efficient and often simpler caching. The second section covers a full multi-stage Docker build, and the last section -covers how to split frontend content into a separate image. +covers how deploy the frontend and backend as separate images. Something that goes for all of these docker deployment strategies is that they are stateless, so for a production deployment you will want to set up and @@ -20,10 +21,10 @@ bundled and served from the backend. This is done using the `@backstage/plugin-app-backend` plugin, which also injects the frontend configuration into the app. This means you that you only need to build and deploy a single container in a minimal setup of Backstage. If you wish to -separate the serving of the frontend out from the backend, see -[the section on that topic below](#separate-frontend). +separate the serving of the frontend out from the backend, see the +[separate frontend](#separate-frontend) topic below. -### Host Build +## Host Build This section describes how to build a Docker image from a Backstage repo with most of the build happening outside of Docker. This is almost always the faster @@ -109,7 +110,7 @@ docker run -it -p 7000:7000 backstage You should then start to get logs in your terminal, and then you can open your browser at `http://localhost:7000` -### Multistage Build +## Multi-stage Build This section describes how to set up a multi-stage Docker build that builds the entire project within Docker. This is typically slower than a host build, but is @@ -207,7 +208,7 @@ docker run -it -p 7000:7000 backstage You should then start to get logs in your terminal, and then you can open your browser at `http://localhost:7000` -### Separate Frontend +## Separate Frontend It is sometimes desirable to serve the frontend separately from the backend, either from a separate image or for example a static file serving provider. The diff --git a/docs/getting-started/deployment-helm.md b/docs/deployment/helm.md similarity index 92% rename from docs/getting-started/deployment-helm.md rename to docs/deployment/helm.md index d1a52a5b30..fd36aeb81f 100644 --- a/docs/getting-started/deployment-helm.md +++ b/docs/deployment/helm.md @@ -1,8 +1,8 @@ --- -id: deployment-helm -title: Helm -description: Documentation on Kubernetes and Helm Deployment -sidebar_label: Kubernetes and Helm +id: helm +title: Deploying Backstage with Helm +description: How to deploy Backstage with Helm and Kubernetes +sidebar_label: Helm --- # Helm charts diff --git a/docs/deployment/heroku.md b/docs/deployment/heroku.md new file mode 100644 index 0000000000..42ab817b30 --- /dev/null +++ b/docs/deployment/heroku.md @@ -0,0 +1,44 @@ +--- +id: heroku +title: Deploying with Heroku +sidebar_label: Heroku +description: How to deploy Backstage to Heroku +--- + +Heroku is a Platform as a Service (PaaS) designed to handle application +deployment in a hands-off way. Heroku supports container deployment of Docker +images, a natural fit for Backstage. + +## Configuring the CLI + +First, install the +[heroku-cli](https://devcenter.heroku.com/articles/heroku-cli) and login: + +```shell +$ heroku login +``` + +Heroku runs a container registry on `registry.heroku.com`. To push Backstage +Docker images, log in to the container registry also: + +```shell +$ heroku container:login +``` + +You _might_ also need to set your Heroku app's stack to `container`: + +```bash +$ heroku stack:set container -a +``` + +## Push and deploy a Docker image + +Now we can push a Backstage [Docker image](docker.md) to Heroku's container +registry and release it to the `web` worker: + +```bash +$ heroku container:push web -a +$ heroku container:release web -a +``` + +Now you should have Backstage up and running! 🎉 diff --git a/docs/deployment/index.md b/docs/deployment/index.md new file mode 100644 index 0000000000..db4ceca6f1 --- /dev/null +++ b/docs/deployment/index.md @@ -0,0 +1,40 @@ +--- +id: index +title: Deploying Backstage +sidebar_title: Overview +description: Packaging Backstage and deploying to production +--- + +Backstage provides tooling to build Docker images, but can be deployed with or +without Docker on many different infrastructures. The _best_ way to deploy +Backstage is in _the same way_ you deploy other software at your organization. + +This documentation shows common examples that may be useful when deploying +Backstage for the first time, or for those without established deployment +practices. + +> Note: The _easiest_ way to explore Backstage is to visit the +> [live demo site](https://demo.backstage.io). + +At Spotify, we deploy software generally by: + +1. Building a Docker image +2. Storing the Docker image on a container registry +3. Referencing the image in a Kubernetes Deployment YAML +4. Applying that Deployment to a Kubernetes cluster + +This method is covered in [Building a Docker image](docker.md) and +[Deploying with Kubernetes](k8s.md). + +There is also an example of deploying on [Heroku](heroku.md), which only +requires the first two steps. + +An example of deploying Backstage with a [Helm chart](helm.md), a common pattern +in AWS, is also available. + +Please consider contributing other deployment guides if you get Backstage set up +on common infrastructure, it would be a great benefit to the community. + +If you need to run Backstage behind a corporate proxy, this +[contributed guide](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md) +may help. diff --git a/docs/deployment/k8s.md b/docs/deployment/k8s.md new file mode 100644 index 0000000000..2b3386ef7c --- /dev/null +++ b/docs/deployment/k8s.md @@ -0,0 +1,519 @@ +--- +id: k8s +title: Deploying with Kubernetes +sidebar_label: Kubernetes +description: How to deploy Backstage to a Kubernetes cluster +--- + +[Kubernetes](https://kubernetes.io/) is a system for deploying, scaling and +managing containerized applications. Backstage is designed to fit this model and +run as a stateless application with an external PostgreSQL database. + +There are many different tools and patterns for Kubernetes clusters, so the best +way to deploy to an existing Kubernetes setup is _the same way_ you deploy +everything else. + +This guide covers basic Kubernetes definitions needed to get Backstage up and +running in a typical cluster. The object definitions might look familiar, since +the Backstage software catalog +[also uses](../features/software-catalog/descriptor-format.md) the Kubernetes +object format! + +## Testing locally + +To test out these concepts locally before deploying to a production Kubernetes +cluster, first install [kubectl](https://kubernetes.io/docs/tasks/tools/), the +Kubernetes command-line tool. + +Next, install [minikube](https://minikube.sigs.k8s.io/docs/start/). This creates +a single-node Kubernetes cluster on your local machine: + +```shell +# Assumes Mac + Homebrew; see the minikube site for other installations +$ brew install minikube +$ minikube start + +... +Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default. +``` + +Now you can run `kubectl` commands and have changes applied to the minikube +cluster. You should be able to see the `kube-system` Kubernetes pods running: + +```shell +$ kubectl get pods -A +``` + +When you're done with the tutorial, use `minikube stop` to halt the cluster and +free up resources. + +## Creating a namespace + +Deployments in Kubernetes are commonly assigned to their own +[namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) +to isolate services in a multi-tenant environment. + +This can be done through `kubectl` directly: + +```shell +$ kubectl create namespace backstage +namespace/backstage created +``` + +Alternatively, create and apply a Namespace definition: + +```yaml +# kubernetes/namespace.yaml +apiVersion: v1 +kind: Namespace +metadata: + name: backstage +``` + +```shell +$ kubectl apply -f kubernetes/namespace.yaml +namespace/backstage created +``` + +## Creating the PostgreSQL database + +Backstage in production uses PostgreSQL as a database. To isolate the database +from Backstage app deployments, we can create a separate Kubernetes deployment +for PostgreSQL. + +### Creating a PostgreSQL secret + +First, create a Kubernetes Secret for the PostgreSQL username and password. This +will be used by both the PostgreSQL and Backstage deployments: + +```yaml +# kubernetes/postgres-secrets.yaml +apiVersion: v1 +kind: Secret +metadata: + name: postgres-secrets + namespace: backstage +type: Opaque +data: + POSTGRES_USER: YmFja3N0YWdl + POSTGRES_PASSWORD: aHVudGVyMg== +``` + +The data in Kubernetes secrets are base64-encoded. The values can be generated +on the command line: + +```shell +$ echo -n "backstage" | base64 +YmFja3N0YWdl +``` + +> Note: Secrets are base64-encoded, but not encrypted. Be sure to enable +> [Encryption at Rest](https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/) +> for the cluster. For storing secrets in Git, consider +> [SealedSecrets or other solutions](https://learnk8s.io/kubernetes-secrets-in-git). + +The secrets can now be applied to the Kubernetes cluster: + +```shell +$ kubectl apply -f kubernetes/postgres-secrets.yaml +secret/postgres-secrets created +``` + +### Creating a PostgreSQL persistent volume + +PostgreSQL needs a persistent volume to store data; we'll create one along with +a `PersistentVolumeClaim`. In this case, we're claiming the whole volume - but +claims can ask for only part of a volume as well. + +```yaml +# kubernetes/postgres-storage.yaml +apiVersion: v1 +kind: PersistentVolume +metadata: + name: postgres-storage + namespace: backstage + labels: + type: local +spec: + storageClassName: manual + capacity: + storage: 2G + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + hostPath: + path: '/mnt/data' +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: postgres-storage-claim + namespace: backstage +spec: + storageClassName: manual + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2G +``` + +This file contains definitions for two different kinds, separate by a line with +a triple dash. This syntax is helpful if you want to consolidate related +Kubernetes definitions in a single file and apply them at the same time. + +Note the volume `type: local`; this creates a volume using local disk on +Kubernetes nodes. More likely in a production scenario, you'd want to use a more +highly available +[type of PersistentVolume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#types-of-persistent-volumes). + +Apply the storage volume and claim to the Kubernetes cluster: + +```shell +$ kubectl apply -f kubernetes/postgres-storage.yaml +persistentvolume/postgres-storage created +persistentvolumeclaim/postgres-storage-claim created +``` + +### Creating a PostgreSQL deployment + +Now we can create a Kubernetes Deployment descriptor for the PostgreSQL database +deployment itself: + +```yaml +# kubernetes/postgres.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres + namespace: backstage +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: postgres:13.2-alpine + imagePullPolicy: 'IfNotPresent' + ports: + - containerPort: 5432 + envFrom: + - secretRef: + name: postgres-secrets + volumeMounts: + - mountPath: /var/lib/postgresql/data + name: postgresdb + volumes: + - name: postgresdb + persistentVolumeClaim: + claimName: postgres-storage-claim +``` + +If you're not used to Kubernetes, this is a lot to take in. We're describing a +Deployment (one or more instances of an application) that we'd like Kubernetes +to know about in the `metadata` block. + +The `spec` block describes the desired state. Here we've requested Kubernetes +create 1 replica (running instance of PostgreSQL), and to create the replica +with the given pod `template`, which again contains Kubernetes metadata and a +desired state. The template `spec` shows one container, created from the +[published](https://hub.docker.com/_/postgres) `postgres:13.2-alpine` Docker +image. + +Note the `envFrom` and `secretRef` - this tells Kubernetes to fill environment +variables in the container with values from the Secret we created. We've also +referenced the volume created for the deployment, and given it the mount path +expected by PostgreSQL. + +Apply the PostgreSQL deployment to the Kubernetes cluster: + +```shell +$ kubectl apply -f kubernetes/postgres.yaml +deployment.apps/postgres created + +$ kubectl get pods --namespace=backstage +NAME READY STATUS RESTARTS AGE +postgres-56c86b8bbc-66pt2 1/1 Running 0 21s +``` + +Verify the deployment by connecting to the pod: + +```shell +$ kubectl exec -it --namespace=backstage postgres-56c86b8bbc-66pt2 -- /bin/bash +bash-5.1# psql -U $POSTGRES_USER +psql (13.2) +backstage=# \q +bash-5.1# exit +``` + +### Creating a PostgreSQL service + +The database pod is running, but how does another pod connect to it? + +Kubernetes pods are transient - they can be killed, restarted, or created +dynamically. Therefore we don't want to try to connect to pods directly, but +rather create a Kubernetes Service. Services keep track of pods and direct +traffic to the right place. + +The final step for our database is to create the service descriptor: + +```yaml +# kubernetes/postgres-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: postgres + namespace: backstage +spec: + selector: + app: postgres + ports: + - port: 5432 +``` + +Apply the service to the Kubernetes cluster: + +```shell +$ kubectl apply -f kubernetes/postgres-service.yaml +service/postgres created + +$ kubectl get services --namespace=backstage +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +postgres ClusterIP 10.96.5.103 5432/TCP 29s +``` + +## Creating the Backstage instance + +Now that we have PostgreSQL up and ready to store data, we can create the +Backstage instance. This follows similar steps as the PostgreSQL deployment. + +### Creating a Backstage secret + +For any Backstage configuration secrets, such as authorization tokens, we can +create a similar Kubernetes Secret as we did +[for PostgreSQL](#creating-a-postgresql-secret), remembering to base64 encode +the values: + +```yaml +# kubernetes/backstage-secrets.yaml +apiVersion: v1 +kind: Secret +metadata: + name: backstage-secrets + namespace: backstage +type: Opaque +data: + GITHUB_TOKEN: VG9rZW5Ub2tlblRva2VuVG9rZW5NYWxrb3ZpY2hUb2tlbg== +``` + +Apply the secret to the Kubernetes cluster: + +```shell +$ kubectl apply -f kubernetes/backstage-secrets.yaml +secret/backstage-secrets created +``` + +### Creating a Backstage deployment + +To create the Backstage deployment, first create a [Docker image](docker.md). +We'll use this image to create a Kubernetes deployment. For this example, we'll +use the standard host build with the frontend bundled and served from the +backend. + +First, create a Kubernetes Deployment descriptor: + +```yaml +# kubernetes/backstage.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backstage + namespace: backstage +spec: + replicas: 1 + selector: + matchLabels: + app: backstage + template: + metadata: + labels: + app: backstage + spec: + containers: + - name: backstage + image: backstage:1.0.0 + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 7000 + envFrom: + - secretRef: + name: postgres-secrets + - secretRef: + name: backstage-secrets +``` + +For production deployments, the `image` reference will usually be a full URL to +a repository on a container registry (for example, ECR on AWS). + +For testing locally with `minikube`, you can point the local Docker daemon to +the `minikube` internal Docker registry and then rebuild the image to install +it: + +```shell +$ eval $(minikube docker-env) +$ yarn build-image --tag backstage:1.0.0 +``` + +There is no special wiring needed to access the PostgreSQL service. Since it's +running on the same cluster, Kubernetes will inject `POSTGRES_SERVICE_HOST` and +`POSTGRES_SERVICE_PORT` environment variables into our Backstage container. +These can be used in the Backstage `app-config.yaml` along with the secrets: + +```yaml +backend: + database: + client: pg + connection: + host: ${POSTGRES_SERVICE_HOST} + port: ${POSTGRES_SERVICE_PORT} + user: ${POSTGRES_USER} + password: ${POSTGRES_PASSWORD} +``` + +Make sure to rebuild the Docker image after applying `app-config.yaml` changes. + +Apply this Deployment to the Kubernetes cluster: + +```shell +$ kubectl apply -f kubernetes/backstage.yaml +deployment.apps/backstage created + +$ kubectl get deployments --namespace=backstage +NAME READY UP-TO-DATE AVAILABLE AGE +backstage 1/1 1 1 1m +postgres 1/1 1 1 10m + +$ kubectl get pods --namespace=backstage +NAME READY STATUS RESTARTS AGE +backstage-54bfcd6476-n2jkm 1/1 Running 0 58s +postgres-56c86b8bbc-66pt2 1/1 Running 0 9m +``` + +Beautiful! 🎉 The deployment and pod are running in the cluster. If you run into +any trouble, check the container logs from the pod: + +```shell +# -f to tail, -c +$ kubectl logs --namespace=backstage -f backstage-54bfcd6476-n2jkm -c backstage +``` + +### Creating a Backstage service + +Like the [PostgreSQL service](#creating-a-postgresql-service) above, we need to +create a Kubernetes Service for Backstage to handle connecting requests to the +correct pods. + +Create the Kubernetes Service descriptor: + +```yaml +# kubernetes/backstage-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: backstage + namespace: backstage +spec: + selector: + app: backstage + ports: + - name: http + port: 80 + targetPort: http +``` + +The `selector` here is telling the Service which pods to target, and the port +mapping translates normal HTTP port 80 to the backend http port (7000) on the +pod. + +Apply this Service to the Kubernetes cluster: + +```shell +$ kubectl apply -f kubernetes/backstage-service.yaml +service/backstage created +``` + +Now we have a fully operational Backstage deployment! 🎉 For a _**grand +reveal**_, you can forward a local port to the service: + +```shell +$ sudo kubectl port-forward --namespace=backstage svc/backstage 80:80 +Forwarding from 127.0.0.1:80 -> 7000 +``` + +This shows port 7000 since `port-forward` doesn't _really_ support services, so +it cheats by looking up the first pod for a service and connecting to the mapped +pod port. + +Note that `app.baseUrl` and `backend.baseUrl` in your `app-config.yaml` should +match what we're forwarding here (port omitted in this example since we're using +the default HTTP port 80): + +```yaml +# app-config.yaml +app: + baseUrl: http://localhost + +organization: + name: Spotify + +backend: + baseUrl: http://localhost + listen: + port: 7000 + cors: + origin: http://localhost +``` + +If you're using an [auth provider](../auth/index.md), it should also have this +address configured for the authentication pop-up to work properly. + +Now you can open a browser on your machine to [localhost](http://localhost) and +browse your Kubernetes-deployed Backstage instance. 🚢🚢🚢 + +## Further steps + +This is most of the way to a full production deployment of Backstage on +Kubernetes. There's a few additional steps to that will likely be needed beyond +the scope of this document. + +### Set up a more reliable volume + +The `PersistentVolume` configured above uses `local` Kubernetes node storage. +This should be replaced with a cloud volume, network attached storage, or +something more persistent beyond a Kubernetes node. + +### Expose the Backstage service + +The Kubernetes Service is not exposed for external connections from outside the +cluster. This is generally done with a Kubernetes +[ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) or +an +[external load balancer](https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/). + +### Update the Deployment image + +To update the Kubernetes deployment to a newly published version of your +Backstage Docker image, update the image tag reference in `backstage.yaml` and +then apply the changes with `kubectl apply -f kubernetes/backstage.yaml`. + +For production purposes, this image tag will generally be a full-fledged URL +pointing to a container registry where built Docker images are hosted. This can +be hosted internally in your infrastructure, or a managed one offered by a cloud +provider. diff --git a/docs/getting-started/deployment-k8s.md b/docs/getting-started/deployment-k8s.md deleted file mode 100644 index bc24894ed1..0000000000 --- a/docs/getting-started/deployment-k8s.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -id: deployment-k8s -title: Kubernetes -description: Documentation on Kubernetes and K8s Deployment ---- - -Backstage itself provides tooling up to the point of building Docker images. -Beyond that point we do not have an opinionated way to deploy Backstage within -Kubernetes, as each cluster has its own unique set of tooling and patterns. - -We do provide examples to help you get started though. Check out -[this example](https://github.com/backstage/backstage/tree/master/contrib/kubernetes/plain_single_backend_deployment/) -for a basic single-deployment setup. diff --git a/docs/getting-started/deployment-other.md b/docs/getting-started/deployment-other.md deleted file mode 100644 index cc622d00f5..0000000000 --- a/docs/getting-started/deployment-other.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -id: deployment-other -title: Other -description: Documentation on different ways of Deployment ---- - -## Heroku - -Deploying to Heroku is relatively easy following these steps. - -First, make sure you have the -[Heroku CLI installed](https://devcenter.heroku.com/articles/heroku-cli) and log -into it as well as login into Heroku's -[container registry](https://devcenter.heroku.com/articles/container-registry-and-runtime). - -```bash -$ heroku login -$ heroku container:login -``` - -You _might_ also need to set your Heroku app's stack to `container`. - -```bash -$ heroku stack:set container -a -``` - -We can now build/push the Docker image to Heroku's container registry and -release it to the `web` worker. - -```bash -$ heroku container:push web -a -$ heroku container:release web -a -``` - -With that, you should have Backstage up and running! diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 64ff108a78..86e94d9fbd 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -22,16 +22,6 @@ "getting-started/app-custom-theme" ] }, - { - "type": "subcategory", - "label": "Deployment", - "ids": [ - "getting-started/deployment-docker", - "getting-started/deployment-k8s", - "getting-started/deployment-helm", - "getting-started/deployment-other" - ] - }, "getting-started/keeping-backstage-updated", "getting-started/concepts", "getting-started/contributors" @@ -211,6 +201,12 @@ "auth/auth-backend-classes", "auth/glossary" ], + "Deployment": [ + "deployment/index", + "deployment/k8s", + "deployment/helm", + "deployment/heroku" + ], "Designing for Backstage": [ "dls/design", "dls/contributing-to-storybook",