Move deployment documentation to its own section

Signed-off-by: Tim Hansen <timbonicus@gmail.com>
This commit is contained in:
Tim Hansen
2021-04-14 20:48:42 -06:00
parent fef852ecd3
commit 7114108898
8 changed files with 623 additions and 71 deletions
@@ -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
@@ -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
+44
View File
@@ -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 <your-app>
```
## 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 <your-app>
$ heroku container:release web -a <your-app>
```
Now you should have Backstage up and running! 🎉
+40
View File
@@ -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.
+519
View File
@@ -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 <none> 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, <pod> -c <container>
$ 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.
-13
View File
@@ -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.
-35
View File
@@ -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 <your-app>
```
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 <your-app>
$ heroku container:release web -a <your-app>
```
With that, you should have Backstage up and running!
+6 -10
View File
@@ -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",