Move deployment documentation to its own section
Signed-off-by: Tim Hansen <timbonicus@gmail.com>
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
---
|
||||
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 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
|
||||
connect to an external PostgreSQL instance where the backend plugins can store
|
||||
their state, rather than using SQLite.
|
||||
|
||||
By default, in an app created with `@backstage/create-app`, the frontend is
|
||||
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
|
||||
[separate frontend](#separate-frontend) topic below.
|
||||
|
||||
## 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
|
||||
approach, as the build steps tend to execute faster, and it's possible to have
|
||||
more efficient caching of dependencies on the host, where a single change won't
|
||||
bust the entire cache.
|
||||
|
||||
The required steps in the host build are to install dependencies with
|
||||
`yarn install`, generate type definitions using `yarn tsc`, and build all
|
||||
packages with `yarn build`.
|
||||
|
||||
> NOTE: If you created your app prior to 2021-02-18, follow the
|
||||
> [migration step](https://github.com/backstage/backstage/releases/tag/release-2021-02-18)
|
||||
> to move from `backend:build` to `backend:bundle`.
|
||||
|
||||
In a CI workflow it might look something like this:
|
||||
|
||||
```bash
|
||||
yarn install --frozen-lockfile
|
||||
|
||||
# tsc outputs type definitions to dist-types/ in the repo root, which are then consumed by the build
|
||||
yarn tsc
|
||||
|
||||
# Build all packages and in the end bundle them all up into the packages/backend/dist folder.
|
||||
yarn build
|
||||
```
|
||||
|
||||
Once the host build is complete, we are ready to build our image. The following
|
||||
`Dockerfile` is included when creating a new app with `@backstage/create-app`:
|
||||
|
||||
```Dockerfile
|
||||
FROM node:14-buster-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy repo skeleton first, to avoid unnecessary docker cache invalidation.
|
||||
# The skeleton contains the package.json of each package in the monorepo,
|
||||
# and along with yarn.lock and the root package.json, that's enough to run yarn install.
|
||||
ADD yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./
|
||||
|
||||
RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)"
|
||||
|
||||
# Then copy the rest of the backend bundle, along with any other files we might want.
|
||||
ADD packages/backend/dist/bundle.tar.gz app-config.yaml ./
|
||||
|
||||
CMD ["node", "packages/backend", "--config", "app-config.yaml"]
|
||||
```
|
||||
|
||||
For more details on how the `backend:bundle` command and the `skeleton.tar.gz`
|
||||
file works, see the
|
||||
[`backend:bundle` command docs](../cli/commands.md#backendbundle).
|
||||
|
||||
The `Dockerfile` is located at `packages/backend/Dockerfile`, but needs to be
|
||||
executed with the root of the repo as the build context, in order to get access
|
||||
to the root `yarn.lock` and `package.json`, along with any other files that
|
||||
might be needed, such as `.npmrc`.
|
||||
|
||||
The `@backstage/create-app` command adds the following `.dockerignore` in the
|
||||
root of the repo to speed up the build by reducing build context size:
|
||||
|
||||
```text
|
||||
.git
|
||||
node_modules
|
||||
packages
|
||||
!packages/backend/dist
|
||||
plugins
|
||||
```
|
||||
|
||||
With the project built and the `.dockerignore` and `Dockerfile` in place, we are
|
||||
now ready to build the final image. From the root of the repo, execute the
|
||||
build:
|
||||
|
||||
```bash
|
||||
docker image build . -f packages/backend/Dockerfile --tag backstage
|
||||
```
|
||||
|
||||
To try out the image locally you can run the following:
|
||||
|
||||
```sh
|
||||
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`
|
||||
|
||||
## 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
|
||||
sometimes desired because Docker in Docker is not available in the build
|
||||
environment, or due to other requirements.
|
||||
|
||||
The build is split into three different stages, where the first stage finds all
|
||||
of the `package.json`s that are relevant for the initial install step enabling
|
||||
us to cache the initial `yarn install` that installs all dependencies. The
|
||||
second stage executes the build itself, and is similar to the steps we execute
|
||||
on the host in the host build. The third and final stage then packages it all
|
||||
together into the final image, and is similar to the `Dockerfile` of the host
|
||||
build.
|
||||
|
||||
The following `Dockerfile` executes the multi-stage build and should be added to
|
||||
the repo root:
|
||||
|
||||
```Dockerfile
|
||||
# Stage 1 - Create yarn install skeleton layer
|
||||
FROM node:14-buster-slim AS packages
|
||||
|
||||
WORKDIR /app
|
||||
COPY package.json yarn.lock ./
|
||||
|
||||
COPY packages packages
|
||||
COPY plugins plugins
|
||||
|
||||
RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 -exec rm -rf {} \+
|
||||
|
||||
# Stage 2 - Install dependencies and build packages
|
||||
FROM node:14-buster-slim AS build
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=packages /app .
|
||||
|
||||
RUN yarn install --frozen-lockfile --network-timeout 600000 && rm -rf "$(yarn cache dir)"
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN yarn tsc
|
||||
RUN yarn --cwd packages/backend backstage-cli backend:bundle --build-dependencies
|
||||
|
||||
# Stage 3 - Build the actual backend image and install production dependencies
|
||||
FROM node:14-buster-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the install dependencies from the build stage and context
|
||||
COPY --from=build /app/yarn.lock /app/package.json /app/packages/backend/dist/skeleton.tar.gz ./
|
||||
RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz
|
||||
|
||||
RUN yarn install --frozen-lockfile --production --network-timeout 600000 && rm -rf "$(yarn cache dir)"
|
||||
|
||||
# Copy the built packages from the build stage
|
||||
COPY --from=build /app/packages/backend/dist/bundle.tar.gz .
|
||||
RUN tar xzf bundle.tar.gz && rm bundle.tar.gz
|
||||
|
||||
# Copy any other files that we need at runtime
|
||||
COPY app-config.yaml ./
|
||||
|
||||
CMD ["node", "packages/backend", "--config", "app-config.yaml"]
|
||||
```
|
||||
|
||||
Note that a newly created Backstage app will typically not have a `plugins/`
|
||||
folder, so you will want to comment that line out. This build also does not work
|
||||
in the main repo, since the `backstage-cli` which is used for the build doesn't
|
||||
end up being properly installed.
|
||||
|
||||
To speed up the build when not running in a fresh clone of the repo you should
|
||||
set up a `.dockerignore`. This one is different than the host build one, because
|
||||
we want to have access to the source code of all packages for the build, but can
|
||||
ignore any existing build output or dependencies:
|
||||
|
||||
```text
|
||||
node_modules
|
||||
packages/*/dist
|
||||
packages/*/node_modules
|
||||
plugins/*/dist
|
||||
plugins/*/node_modules
|
||||
```
|
||||
|
||||
Once you have added both the `Dockerfile` and `.dockerignore` to the root of
|
||||
your project, run the following to build the container under a specified tag.
|
||||
|
||||
```sh
|
||||
docker image build -t backstage .
|
||||
```
|
||||
|
||||
To try out the image locally you can run the following:
|
||||
|
||||
```sh
|
||||
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
|
||||
|
||||
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
|
||||
first step in doing so is to remove the `app-backend` plugin from the backend
|
||||
package, which is done as follows:
|
||||
|
||||
1. Delete `packages/backend/src/plugins/app.ts`
|
||||
2. Remove the following lines from `packages/backend/src/index.ts`:
|
||||
```tsx
|
||||
import app from './plugins/app';
|
||||
// ...
|
||||
const appEnv = useHotMemoize(module, () => createEnv('app'));
|
||||
// ...
|
||||
.addRouter('', await app(appEnv));
|
||||
```
|
||||
3. Remove the `@backstage/plugin-app-backend` and the app package dependency
|
||||
(e.g. `app`) from `packages/backend/packages.json`. If you don't remove the
|
||||
app package dependency the app will still be built and bundled with the
|
||||
backend.
|
||||
|
||||
Once the `app-backend` is removed from the backend, you can use your favorite
|
||||
static file serving method for serving the frontend. An example of how to set up
|
||||
an NGINX image is available in the
|
||||
[contrib folder in the main repo](https://github.com/backstage/backstage/blob/master/contrib/docker/frontend-with-nginx/Dockerfile)
|
||||
|
||||
Note that if you're building a separate docker build of the frontend you
|
||||
probably need to adjust `.dockerignore` appropriately. Most likely by making
|
||||
sure `packages/app/dist` is not ignored.
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
id: helm
|
||||
title: Deploying Backstage with Helm
|
||||
description: How to deploy Backstage with Helm and Kubernetes
|
||||
sidebar_label: Helm
|
||||
---
|
||||
|
||||
# Helm charts
|
||||
|
||||
An example Backstage app can be deployed in Kubernetes using the
|
||||
[Backstage Helm charts](https://github.com/backstage/backstage/tree/master/contrib/chart/backstage).
|
||||
|
||||
First, choose a DNS name where Backstage will be hosted, and create a YAML file
|
||||
for your custom configuration.
|
||||
|
||||
```yaml
|
||||
appConfig:
|
||||
app:
|
||||
baseUrl: https://backstage.mydomain.com
|
||||
title: Backstage
|
||||
backend:
|
||||
baseUrl: https://backstage.mydomain.com
|
||||
cors:
|
||||
origin: https://backstage.mydomain.com
|
||||
lighthouse:
|
||||
baseUrl: https://backstage.mydomain.com/lighthouse-api
|
||||
techdocs:
|
||||
storageUrl: https://backstage.mydomain.com/api/techdocs/static/docs
|
||||
requestUrl: https://backstage.mydomain.com/api/techdocs
|
||||
```
|
||||
|
||||
Then use it to run:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/backstage/backstage.git
|
||||
cd backstage/contrib/chart/backstage
|
||||
helm dependency update
|
||||
helm install -f backstage-mydomain.yaml backstage .
|
||||
```
|
||||
|
||||
This command will deploy the following pieces:
|
||||
|
||||
- Backstage frontend
|
||||
- Backstage backend with scaffolder and auth plugins
|
||||
- (optional) a PostgreSQL instance
|
||||
- lighthouse plugin
|
||||
- ingress
|
||||
|
||||
After a few minutes Backstage should be up and running in your cluster under the
|
||||
DNS specified earlier.
|
||||
|
||||
Make sure to create the appropriate DNS entry in your infrastructure. To find
|
||||
the public IP address run:
|
||||
|
||||
```bash
|
||||
$ kubectl get ingress
|
||||
NAME HOSTS ADDRESS PORTS AGE
|
||||
backstage-ingress * 123.1.2.3 80 17m
|
||||
```
|
||||
|
||||
> **NOTE**: this is not a production ready deployment.
|
||||
|
||||
For more information on how to customize the deployment check the
|
||||
[README](https://github.com/backstage/backstage/tree/master/contrib/chart/backstage/README.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 <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! 🎉
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user