Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mrc-4908 k8s setup #1

Merged
merged 17 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
apache/ssl
k8s/ssl
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Some applications (copied over from the original deployment are in `apps`). Thes

## Bringing the bits up

```
```bash
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest you remove the docker instructions and the haproxy/httpd bits entirely

docker network create twinkle 2> /dev/null || /bin/true
docker volume create shiny_logs
docker run -d --name haproxy --network twinkle mrcide/haproxy:dev
Expand All @@ -55,7 +55,26 @@ docker exec haproxy update_shiny_servers shiny 1

Teardown

```
```bash
docker rm -f haproxy apache shiny-1
docker network rm twinkle
```

## Running in Kubernetes

The following is guide to run the shiny server in kubernetes. They are based on running a Kind cluster. The configuration may
nee to be adjusted for other k8s clusters.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
nee to be adjusted for other k8s clusters.
need to be adjusted for other k8s clusters.


### Prerequisites

A production kubernetes cluster using k3s is needed to be setup first. To setup a k8s cluster follow the guide [here](https://mrc-ide.myjetbrains.com/youtrack/articles/RESIDE-A-31/Setting-up-Kubernetes-k8s-Cluster). Note: If using dev cluster (KIND), the storageClassName: local-path & longhorn is unavailable and you will need to provision own storage class and persistant volume.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the recommended setup look like for developing this locally? or on a staging machine (separate from the shiny staging, which is really for the users)? Do we need a second cluster somewhere that we use?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the link shows the dev and prod setup and they both can be used locally... for staging, we can use the same cluster and just do another deployment and rename the deployment to something else... But if you wanted a seperate machine for just staging spinning up another cluster might be easiest

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
A production kubernetes cluster using k3s is needed to be setup first. To setup a k8s cluster follow the guide [here](https://mrc-ide.myjetbrains.com/youtrack/articles/RESIDE-A-31/Setting-up-Kubernetes-k8s-Cluster). Note: If using dev cluster (KIND), the storageClassName: local-path & longhorn is unavailable and you will need to provision own storage class and persistant volume.
A production kubernetes cluster using k8s is needed to be setup first. To setup a k8s cluster follow the guide [here](https://mrc-ide.myjetbrains.com/youtrack/articles/RESIDE-A-31/Setting-up-Kubernetes-k8s-Cluster). Note: If using dev cluster (KIND), the storageClassName: local-path & longhorn is unavailable and you will need to provision own storage class and persistent volume.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

storageClassName: local-path & longhorn is unavailable and you will need to provision own storage class and persistant volume.

Sorry, don't really understand this. Why aren't these available? You talk about using local-path below, in persistance.yaml.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have updated to avoid confusion


Run `start-k8s-shiny <env>` to run the shint server in k8s.


#### Teardown

Run the following:

1. `kubectl delete -k k8s/overlays/<env>`. Replace <env> with staging or production.
2. `kubectl delete ns twinkle` to remove namespace.
1 change: 1 addition & 0 deletions haproxy/haproxy.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ frontend http-in
option forwardfor
default_backend servers

# point this to K8s master nodes (control planes)
backend servers
# I think that leastconn is probably the best bet here.
balance leastconn
Expand Down
38 changes: 38 additions & 0 deletions k8s/base/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: shiny-deploy
labels:
app: shiny
spec:
replicas: 2
selector:
matchLabels:
app: shiny
template:
metadata:
labels:
app: shiny
spec:
initContainers:
- name: init-shiny
image: busybox:1.28
command: ["sh", "-c", "mkdir -p /shiny/logs /shiny/apps"]
volumeMounts:
- name: shiny-data
mountPath: /shiny
containers:
- name: shiny
image: absternator/shiny-server:dev # todo: change to actual one!!! mrcide-
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What changes did you make here? Can we get the image build script/Dockerfile here and building on BK?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just changed the image so it has an image to pull from the docker hub... i can add the docker build & push step to the startup script? is that you want? ps.. sorry whats BK?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BK=BuildKite, where we have some non-gha CI builds running,

volumeMounts:
- name: shiny-data
mountPath: /shiny
# todo: create appropriate resource requests and limits
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the consequence of these limits? Are they speculative (affecting scheduling) or caps (meaning the scheduler kills processes that exceed them)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have updated comments but i think usually requests are set or nothing is fine most of the time ... the most common is to set up requests so that each pod has allocated specific resources before being scheduled...

# resources:
# requests:
# memory: "128Mi"
# cpu: "250m"
volumes:
- name: shiny-data
persistentVolumeClaim:
claimName: shiny-pvc
26 changes: 26 additions & 0 deletions k8s/base/ingress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-shiny
annotations:
nginx.ingress.kubernetes.io/affinity: "cookie"
nginx.ingress.kubernetes.io/session-cookie-name: "shinycookie"
nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this in seconds? 48 hours? Seems reasonable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup in seconds... 48 hours

nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"
spec:
ingressClassName: nginx
tls:
- hosts:
- shiny-dev.dide.ic.ac.uk

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we'll need to update this setting for prod, and for running locally?

secretName: tls-secret
rules:
- host: shiny-dev.dide.ic.ac.uk
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: shiny-svc
port:
number: 3838
12 changes: 12 additions & 0 deletions k8s/base/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
metadata:
name: shiny

namespace: twinkle # assume this namespace exists

resources:
- persistance.yaml

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- persistance.yaml
- persistence.yaml

- deployment.yaml
- service.yaml
- ingress.yaml
13 changes: 13 additions & 0 deletions k8s/base/persistance.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# todo: storage capacity as per requirements
# can use local-path if single node or dont need distributed & replicated
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: shiny-pvc
spec:
storageClassName: longhorn
accessModes:
- ReadWriteMany
resources:
requests:
storage: 15Gi
12 changes: 12 additions & 0 deletions k8s/base/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: shiny-svc
spec:
type: ClusterIP
selector:
app: shiny
ports:
- protocol: TCP
port: 3838
targetPort: 3838
14 changes: 14 additions & 0 deletions k8s/configure_ssl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash

export VAULT_ADDR=https://vault.dide.ic.ac.uk:8200
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a k8s/vault adaptor to make this easier? Not necessarily for this PR, but feels like something we might do a bunch of we do more with this style.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

il have a look into that... in the past when we stored secerts in azure key vault we would run and get the secrets in CI and create the secret that way so kubernetes could use it... but il see if there is other ways

export VAULT_TOKEN=$(vault login -method=github -token-only)

DEST=${PWD}/k8s/ssl
mkdir -p $DEST

vault read -field=value /secret/shiny.dide/dev/ssl/cert > \
${DEST}/certificate.pem
vault read -field=value /secret/shiny.dide/dev/ssl/key > \
${DEST}/key.pem

kubectl -n twinkle create secret tls tls-secret --cert ${DEST}/certificate.pem --key ${DEST}/key.pem
26 changes: 26 additions & 0 deletions k8s/krsync
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this script do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pretty much coppies files from host machine into a kubernetes pod using rsync


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest some hardening of your bash scripts (basically any time you write one). In general we end up with:

set -eu

which will cause the script to fail if any line errors or if you reference an undefined variable.

# https://serverfault.com/questions/741670/rsync-files-to-a-kubernetes-pod
# example usage:
# ./krsync -av --progress --stats <hostdir> podName@namespace:<poddir>

if [ -z "$KRSYNC_STARTED" ]; then
export KRSYNC_STARTED=true
exec rsync -a --delete --blocking-io --rsh "$0" $@
fi

# Running as --rsh
namespace=''
pod=$1
shift

# If user uses pod@namespace, rsync passes args as: {us} -l pod namespace ...
if [ "X$pod" = "X-l" ]; then
pod=$1
shift
namespace="-n $1"
shift
fi

# customise this to cluster needs!!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What sort of customisation is expected here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loose comment when i first did have now removed

exec kubectl $namespace exec -i $pod -- "$@"
12 changes: 12 additions & 0 deletions k8s/overlays/production/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤨

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😕 ?


resources:
- ../../base

commonLabels:
env: production

replicas:
- name: shiny-deploy
count: 9
12 changes: 12 additions & 0 deletions k8s/overlays/staging/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: kustomize.config.k8s.io/v1beta1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably will need a name for what the users think of staging that won't confuse us - preview or testing perhaps?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have changed naming to testing... because you are right we dont have a specific staging k8s cluster

kind: Kustomization

resources:
- ../../base

commonLabels:
env: staging

replicas:
- name: shiny-deploy
count: 3
11 changes: 11 additions & 0 deletions k8s/shiny-sync
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is just a temporary thing until we have the management bit working?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup temporary until we sort the management side


# uses krysnc to sync files to a kubernetes pod and thus into k8s cluster (via persistant volume)
# RUN this from repository root to ensure correct paths to app files
NAMESPACE=twinkle
SYNC_DIR=/shiny/apps


FIRST_SHINY_POD=$(kubectl get pods -l app=shiny -n $NAMESPACE -o jsonpath='{.items[0].metadata.name}')
echo "pod name: $FIRST_SHINY_POD"
$PWD/k8s/krsync -av --progress --stats $PWD/apps/* $FIRST_SHINY_POD@$NAMESPACE:$SYNC_DIR
5 changes: 3 additions & 2 deletions shiny/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ FROM rocker/shiny:latest
# Override the rocker defaults with our defaults
COPY shiny-server.conf /etc/shiny-server/shiny-server.conf

VOLUME /shiny/apps
VOLUME /shiny/logs
# Install rsync
RUN apt-get update && apt-get install -y rsync

2 changes: 1 addition & 1 deletion shiny/build
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -e
HERE=$(realpath $(dirname $0))
IMAGE_NAME="mrcide/shiny-server"
IMAGE_NAME="absternator/shiny-server"
TAG_VERSION=dev
docker pull rocker/shiny:latest
docker build --rm \
Expand Down
31 changes: 31 additions & 0 deletions start-k8s-shiny
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env bash

# example use case: ./start-k8s-shiny production

# Set the environment variable to the first command-line argument or default to 'staging'
ENV=${1:-staging}
if [[ "$ENV" != "staging" && "$ENV" != "production" ]]; then
echo "Error: env must be either 'staging' or 'production'"
exit 1
fi
# Make the script itself executable
chmod +x "$0"

# Make the k8s directory and its contents executable
chmod +x k8s/

# Create a Kubernetes namespace named 'twinkle'
kubectl create ns twinkle

# Run the script to configure SSL (assuming it's in the k8s directory)
k8s/configure_ssl || { echo "Error: Failed to configure SSL"; exit 1; }

# Apply Kubernetes manifests from overlays based on the specified or default environment
kubectl apply -k "k8s/overlays/$ENV"

# Wait for the 'shiny-deploy' deployment to be in the 'available' condition within the 'twinkle' namespace
echo 'Waiting for pods to be ready...'
kubectl wait -n twinkle --for=condition=available --timeout=300s deployment/shiny-deploy

# Run the script for 'shiny-sync' (assuming it's in the k8s directory)
k8s/shiny-sync