Skip to content

Commit 9775aed

Browse files
author
Jody McIntyre
authored
Merge pull request #1 from plotly/dockerize
Dockerize
2 parents a079c36 + 38f8295 commit 9775aed

File tree

16 files changed

+377
-5
lines changed

16 files changed

+377
-5
lines changed

.circleci/config.yml

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,16 @@
99

1010
workflows:
1111
version: 2
12-
test:
12+
test-and-push:
1313
jobs:
14-
- node-v6
15-
- node-v8
14+
- test-node-v6
15+
- test-node-v8
16+
- docker-build
17+
- docker-push:
18+
requires:
19+
- test-node-v6
20+
- test-node-v8
21+
- docker-build
1622

1723
version: 2
1824
jobs:
@@ -53,12 +59,60 @@ jobs:
5359
- store_artifacts:
5460
path: build
5561

56-
node-v6:
62+
test-node-v6:
5763
<<: *base
5864
docker:
5965
- image: circleci/node:6.10.3-browsers
6066

61-
node-v8:
67+
test-node-v8:
6268
<<: *base
6369
docker:
6470
- image: circleci/node:8.4.0-browsers
71+
72+
docker-build:
73+
docker:
74+
- image: circleci/node:6.10.3-browsers
75+
76+
steps:
77+
- setup_remote_docker:
78+
reusable: true
79+
80+
- checkout
81+
82+
- run:
83+
name: Build Docker image
84+
command: |
85+
docker build -f deployment/Dockerfile -t gcr.io/${GOOGLE_PROJECT_ID}/imageserver:$CIRCLE_SHA1 .
86+
87+
- run:
88+
name: Smoke test Docker image
89+
command: |
90+
docker run -d -p 9091:9091/tcp --name imageserver gcr.io/${GOOGLE_PROJECT_ID}/imageserver:$CIRCLE_SHA1
91+
docker run --network container:imageserver appropriate/curl --retry 60 --retry-connrefused --retry-delay 1 http://localhost:9091/ping
92+
93+
docker-push:
94+
docker:
95+
- image: circleci/node:6.10.3-browsers
96+
97+
steps:
98+
- setup_remote_docker:
99+
reusable: true
100+
101+
- run:
102+
name: Install gcloud
103+
command: |
104+
echo "deb http://packages.cloud.google.com/apt cloud-sdk-jessie main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
105+
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
106+
sudo apt-get update && sudo apt-get install google-cloud-sdk kubectl
107+
108+
- run:
109+
name: Push Docker image to GCR
110+
command: |
111+
echo ${GOOGLE_AUTH} | base64 -i --decode > ${HOME}/gcp-key.json
112+
gcloud auth activate-service-account --key-file ${HOME}/gcp-key.json
113+
gcloud --quiet config set project ${GOOGLE_PROJECT_ID}
114+
gcloud --quiet config set compute/zone ${GOOGLE_COMPUTE_ZONE}
115+
116+
docker tag gcr.io/${GOOGLE_PROJECT_ID}/imageserver:$CIRCLE_SHA1 gcr.io/${GOOGLE_PROJECT_ID}/imageserver:$CIRCLE_BRANCH
117+
gcloud docker -- push gcr.io/${GOOGLE_PROJECT_ID}/imageserver:$CIRCLE_SHA1
118+
gcloud docker -- push gcr.io/${GOOGLE_PROJECT_ID}/imageserver:$CIRCLE_BRANCH

.dockerignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
npm-debug.log*
3+
4+
build
5+
test

deployment/Dockerfile

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
FROM ubuntu:trusty
2+
3+
####################
4+
# Install node and dependencies
5+
# From: https://github.com/nodejs/docker-node/blob/master/6.11/Dockerfile
6+
7+
RUN apt-get update && apt-get install -y --no-install-recommends \
8+
gnupg curl ca-certificates xz-utils wget \
9+
&& rm -rf /var/lib/apt/lists/* && apt-get clean
10+
11+
RUN groupadd --gid 1000 node \
12+
&& useradd --uid 1000 --gid node --shell /bin/bash --create-home node
13+
14+
# gpg keys listed at https://github.com/nodejs/node#release-team
15+
RUN set -ex \
16+
&& for key in \
17+
9554F04D7259F04124DE6B476D5A82AC7E37093B \
18+
94AE36675C464D64BAFA68DD7434390BDBE9B9C5 \
19+
FD3A5288F042B6850C66B31F09FE44734EB7990E \
20+
71DCFD284A79C3B38668286BC97EC7A07EDE3FC1 \
21+
DD8F2338BAE7501E3DD5AC78C273792F7D83545D \
22+
B9AE9905FFD7803F25714661B63B535A4C206CA9 \
23+
C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 \
24+
56730D5401028683275BD23C23EFEFE93C4CFFFE \
25+
; do \
26+
gpg --keyserver pgp.mit.edu --recv-keys "$key" || \
27+
gpg --keyserver keyserver.pgp.com --recv-keys "$key" || \
28+
gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key" ; \
29+
done
30+
31+
ENV NPM_CONFIG_LOGLEVEL info
32+
ENV NODE_VERSION 6.11.3
33+
34+
RUN ARCH= && dpkgArch="$(dpkg --print-architecture)" \
35+
&& case "${dpkgArch##*-}" in \
36+
amd64) ARCH='x64';; \
37+
ppc64el) ARCH='ppc64le';; \
38+
s390x) ARCH='s390x';; \
39+
arm64) ARCH='arm64';; \
40+
*) echo "unsupported architecture"; exit 1 ;; \
41+
esac \
42+
&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-$ARCH.tar.xz" \
43+
&& curl -SLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
44+
&& gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
45+
&& grep " node-v$NODE_VERSION-linux-$ARCH.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
46+
&& tar -xJf "node-v$NODE_VERSION-linux-$ARCH.tar.xz" -C /usr/local --strip-components=1 \
47+
&& rm "node-v$NODE_VERSION-linux-$ARCH.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
48+
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs
49+
50+
####################
51+
# Download fonts
52+
53+
RUN apt-get update -y && apt-get install -y subversion fontconfig && \
54+
rm -rf /var/lib/apt/lists/* && apt-get clean && \
55+
cd /usr/share/fonts/truetype && \
56+
for font in \
57+
https://github.com/google/fonts/trunk/apache/droidsansmono \
58+
https://github.com/google/fonts/trunk/apache/droidsans \
59+
https://github.com/google/fonts/trunk/apache/droidserif \
60+
https://github.com/google/fonts/trunk/apache/roboto \
61+
https://github.com/google/fonts/trunk/apache/opensans \
62+
https://github.com/google/fonts/trunk/ofl/gravitasone \
63+
https://github.com/google/fonts/trunk/ofl/oldstandardtt \
64+
https://github.com/google/fonts/trunk/ofl/ptsansnarrow \
65+
https://github.com/google/fonts/trunk/ofl/raleway \
66+
https://github.com/google/fonts/trunk/ofl/overpass \
67+
; do \
68+
svn checkout $font ; \
69+
done && \
70+
mkdir /usr/share/fonts/user && \
71+
fc-cache -fv && apt-get --auto-remove -y remove subversion
72+
73+
####################
74+
# Copy and set up image-exporter
75+
76+
RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
77+
sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list' && \
78+
apt-get update -y && \
79+
apt-get install -y google-chrome-stable xvfb && \
80+
rm -rf /var/lib/apt/lists/* && apt-get clean
81+
82+
COPY package.json /var/www/image-exporter/
83+
COPY bin /var/www/image-exporter/bin
84+
COPY src /var/www/image-exporter/src
85+
86+
WORKDIR /var/www/image-exporter
87+
RUN npm install && mkdir build
88+
89+
COPY deployment/run_server /
90+
91+
EXPOSE 9091
92+
ENTRYPOINT ["/run_server"]
93+
CMD ["--plotlyJS", "https://plot.ly/static/plotlyjs/build/plotlyjs-bundle.js"]

deployment/README.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Deployment to GKE using Docker and Kubernetes
2+
3+
## Provision:
4+
5+
This is done once, manually.
6+
7+
```
8+
gcloud beta container clusters create imageserver-stage --enable-autoscaling --min-nodes=1 --max-nodes=3 --num-nodes=1 --zone=us-central1-a --additional-zones=us-central1-b,us-central1-c --enable-autoupgrade --cluster-version=1.7.6-gke.1
9+
# Note: "min", "num", and "max" nodes sets the number PER ZONE.
10+
kubectl apply -f deployment/kube
11+
kubectl get service imageserver # Will show the load balancer IP when it's ready
12+
```
13+
14+
## Build & push:
15+
16+
Builds are performed automatically by CircleCI, and if all tests pass the image
17+
will be pushed to GCR. Images are tagged using the branch name and the
18+
sha1 of the git commit.
19+
20+
## Deploy, plotly.js upgrade, rollback
21+
22+
This is done using plotbot. The following commands provide help:
23+
24+
```
25+
@plotbot deploy how
26+
@plotbot run how
27+
```
28+
29+
# Font Support
30+
31+
The image server ships with many built-in fonts (see the Dockerfile for a list)
32+
and also supports external fonts. External fonts are intended for restrictively
33+
licensed fonts that we can not ship as part of the open source release, and
34+
may also be used by 3rd party users to install their own fonts (restrictively
35+
licensed or open source).
36+
37+
On boot, the image server looks for fonts in `/usr/share/fonts/user` and will
38+
use any valid font found there. You may map a directory into this location in
39+
the container.
40+
41+
In GKE, the pod requires a GCE Persistent Disk called
42+
`plotly-cloud-licensed-fonts` that contains the restrictively licensed fonts
43+
used by Plotly Cloud. To update fonts:
44+
45+
1. In the `PlotlyCloud` project, create a disk from the
46+
`plotly-cloud-licensed-fonts` image and attach it to a GCE VM.
47+
48+
2. Reboot the GCE VM, then mount /dev/sdb to a temporary directory.
49+
50+
3. Add/remove/update fonts in this temporary directory.
51+
52+
4. Unmount the temporary directory and detach the disk from the VM.
53+
54+
5. Delete the `plotly-cloud-licensed-fonts` image and re-create it from the disk.
55+
56+
6. Create new `plotly-cloud-licensed-fonts` persistent disks from the image:
57+
58+
```
59+
for zone in us-central1-a us-central1-b us-central1-c ; do
60+
gcloud compute disks create plotly-cloud-licensed-fonts --image-project=sunlit-shelter-132119 --image=plotly-cloud-licensed-fonts --zone $zone
61+
done
62+
```
63+
64+
# Mapbox Access Token
65+
66+
In order to use the Mapbox functionality built in to plotly.js, a Mapbox
67+
access token must be provided. This can be part of the plot JSON, but for cases
68+
where it is not included in the plot JSON it is useful to have a default.
69+
70+
To specify one, add it as a Kubernetes secret:
71+
72+
```
73+
echo -n "pk.whatever.blabla" > /tmp/token
74+
kubectl create secret generic mapbox --from-file=default_access_token=/tmp/token
75+
```
76+
77+
After adding the secret for the first time or it changes, you'll need to recreate
78+
all pods. The easiest way to do this is by running the update_plotlyjs command.

deployment/kube/frontend.yaml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
apiVersion: extensions/v1beta1
2+
kind: Deployment
3+
metadata:
4+
name: imageserver
5+
labels:
6+
app: imageserver
7+
spec:
8+
replicas: 3
9+
template:
10+
metadata:
11+
labels:
12+
app: imageserver
13+
tier: frontend
14+
spec:
15+
affinity:
16+
podAntiAffinity:
17+
requiredDuringSchedulingIgnoredDuringExecution:
18+
- labelSelector:
19+
matchExpressions:
20+
- key: "app"
21+
operator: In
22+
values:
23+
- imageserver
24+
topologyKey: "kubernetes.io/hostname"
25+
containers:
26+
- name: imageserver-app
27+
image: gcr.io/sunlit-shelter-132119/imageserver
28+
args: ["--plotlyJS", "https://stage.plot.ly/static/plotlyjs/build/plotlyjs-bundle.js"]
29+
env:
30+
- name: PLOTLY_MAPBOX_DEFAULT_ACCESS_TOKEN
31+
valueFrom:
32+
secretKeyRef:
33+
name: mapbox
34+
key: default_access_token
35+
# This setting makes nodes pull the docker image every time before
36+
# starting the pod. This is useful when debugging, but should be turned
37+
# off in production.
38+
imagePullPolicy: Always
39+
ports:
40+
- name: http-server
41+
containerPort: 9091
42+
volumeMounts:
43+
- mountPath: "/usr/share/fonts/user"
44+
name: plotly-cloud-licensed-fonts
45+
livenessProbe:
46+
httpGet:
47+
path: /ping
48+
port: 9091
49+
volumes:
50+
- name: plotly-cloud-licensed-fonts
51+
gcePersistentDisk:
52+
pdName: plotly-cloud-licensed-fonts
53+
readOnly: true
54+
fsType: ext4

deployment/kube/hpa.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
apiVersion: autoscaling/v1
2+
kind: HorizontalPodAutoscaler
3+
metadata:
4+
name: imageserver
5+
spec:
6+
scaleTargetRef:
7+
apiVersion: extensions/v1beta1
8+
kind: Deployment
9+
name: imageserver
10+
# Set this to 3x "min-nodes":
11+
minReplicas: 3
12+
# Set this to 3x "max-nodes":
13+
maxReplicas: 9
14+
# This should be raised to 80 for production:
15+
targetCPUUtilizationPercentage: 50

deployment/kube/loadbalancer.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
name: imageserver
5+
annotations:
6+
cloud.google.com/load-balancer-type: "internal"
7+
labels:
8+
app: imageserver
9+
spec:
10+
type: LoadBalancer
11+
ports:
12+
- port: 9091
13+
protocol: TCP
14+
selector:
15+
app: imageserver

deployment/playbook_stage.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
- hosts: localhost
2+
gather_facts: False
3+
vars:
4+
cluster: imageserver-stage
5+
zone: us-central1-a
6+
roles:
7+
- common
8+
- update
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
- hosts: localhost
2+
gather_facts: False
3+
vars:
4+
cluster: imageserver-stage
5+
zone: us-central1-a
6+
roles:
7+
- common
8+
- rollback
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
- hosts: localhost
2+
gather_facts: False
3+
vars:
4+
cluster: imageserver-stage
5+
zone: us-central1-a
6+
roles:
7+
- common
8+
- update_plotlyjs

0 commit comments

Comments
 (0)