Skip to content

Commit

Permalink
INFRA-8460 Initial kabuild container (#65)
Browse files Browse the repository at this point in the history
Note: Naming and location of this container is not final.
It may be better located in webapp. Nevertheless, it is
useful now in several ways.

* Try to do everything that linux-setup.sh does
* Enough debug tools that this container is useful for debugging things

Issue: https://khanacademy.atlassian.net/browse/INFRA-8460

Test plan:

Run the following commands:
- make build
- make run_dirty

Check that ~/khan contains local ~/khan and that git works.
This is likely very slow on a mac, so be careful.

Ideally, try a clean checkout of webapp somewhere followed by
"make deps". This is probably best tested on a linux machine.
  • Loading branch information
Eric Brown authored Oct 3, 2022
1 parent 830604f commit 17a9920
Show file tree
Hide file tree
Showing 9 changed files with 368 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/**/packer_cache/
/**/builds/
.vscode
153 changes: 153 additions & 0 deletions containers/kabuild/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# This is meant to be a base image for building (and possibly running) webapp.
# It implements most of linux-setup.sh.

# Warning: We're using a number of "deprecated" packages and moving past 22.04
# is likely non-trivial.
FROM ubuntu:22.04

# TODO: Use linux-setup.sh for everything.
# Warning: I hit a number of snags trying to use linux-setup.sh directly.

RUN apt-get update && apt-get install -y \
curl \
git \
jq \
make \
curl \
unzip \
net-tools \
telnet \
wget \
screen \
tree \
netcat \
strace \
tcpdump \
tcpflow \
sudo \
software-properties-common \
apt-transport-https \
ca-certificates \
gnupg

# We're in a container. We aren't root just to avoid stupid mistakes.
# But we want to be root easily if needed.
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

# TODO: Lock down google-sdk version as we do elsewhere.

# Install google-sdk repo
RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
RUN echo "deb https://packages.cloud.google.com/apt cloud-sdk main" >> /etc/apt/sources.list.d/google-cloud-sdk.list
# Install golang repo
RUN add-apt-repository -y ppa:longsleep/golang-backports
RUN add-apt-repository -y -r ppa:chris-lea/node.js
RUN add-apt-repository -y ppa:git-core/ppa

RUN wget -O- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | sudo apt-key add -
RUN echo deb https://deb.nodesource.com/node_16.x `lsb_release -c -s` main >> /etc/apt/sources.list.d/nodesource.list
RUN echo deb-src https://deb.nodesource.com/node_16.x `lsb_release -c -s` main >> /etc/apt/sources.list.d/nodesource.list
RUN chmod a+rX /etc/apt/sources.list.d/nodesource.list
RUN echo "Package: nodejs" >> /etc/apt/preferences.d/nodejs
RUN echo "Pin: version 16.*" >> /etc/apt/preferences.d/nodejs
RUN echo "Pin-Priority: 999" >> /etc/apt/preferences.d/nodejs

# Update indexes
RUN apt-get update

# Install google cloud sdk
RUN apt-get install -y \
google-cloud-sdk \
openjdk-11-jdk \
nodejs \
watchman

# TODO: Get version from common location
# WARNING: We REALLY WANT to avoid requring make and/or break standalone skaffold.
ENV DESIRED_GO_VERSION=1.16
RUN apt-get install -y golang-${DESIRED_GO_VERSION}
RUN cp -sf /usr/lib/go-${DESIRED_GO_VERSION}/bin/* /usr/local/bin/

# Python2
ENV VENV=.virtualenv/khan27
RUN apt-get install -y python2-dev python-setuptools
RUN ln -s /usr/bin/python2 /usr/bin/python
WORKDIR /tmp
RUN curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output get-pip.py
RUN python2 get-pip.py pip==19.3.1
RUN rm -f get-pip.py
WORKDIR /root
RUN pip install virtualenv==20.0.23
RUN pip install http://sourceforge.net/projects/pychecker/files/pychecker/0.8.19/pychecker-0.8.19.tar.gz/download
RUN virtualenv -q --python="$(which python2)" --always-copy "/root/${VENV}"
RUN /root/${VENV}/bin/pip install -U "pip<20" setuptools

RUN apt-get install -y \
libfreetype6 libfreetype6-dev libpng-dev libjpeg-dev \
imagemagick \
libxslt1-dev \
libyaml-dev \
libncurses-dev libreadline-dev \
nginx \
redis-server \
libnss3-tools \
python3-pip

RUN pip3 install -q pipenv
RUN npm install -g yarn

# protoc
RUN mkdir -p /tmp/protoc
RUN wget -O /tmp/protoc/protoc-3.4.0.zip https://github.com/google/protobuf/releases/download/v3.4.0/protoc-3.4.0-linux-x86_64.zip
WORKDIR /tmp/protoc
RUN unzip -q protoc-3.4.0.zip
RUN install -m755 ./bin/protoc /usr/local/bin
RUN rm -rf /usr/local/include/google/protobuf
RUN mkdir -p /usr/local/include/google
RUN mv ./include/google/protobuf /usr/local/include/google/
RUN chmod -R a+rX /usr/local/include/google/protobuf
WORKDIR /root
RUN rm -rf /tmp/protoc

WORKDIR /root

ENV GROUP_NAME=khandev
ENV USER_NAME=khandev
RUN addgroup --gid 1001 ${GROUP_NAME}
# To set password to "kabuild", add -p padco2M.JnJHY
RUN useradd -ms /bin/bash --uid 1001 --gid 1001 -G sudo ${USER_NAME}

RUN mkdir /tmp/fastly
RUN cd /tmp/fastly && \
curl -LO https://github.com/fastly/cli/releases/download/v3.3.0/fastly_3.3.0_linux_amd64.deb && \
apt install ./fastly_3.3.0_linux_amd64.deb
RUN rm -rf /tmp/fastly

USER ${USER_NAME}:${GROUP_NAME}
WORKDIR /home/${USER_NAME}

RUN mkdir -m 700 .ssh
RUN mkdir khan
RUN ssh-keyscan github.com >> ~/.ssh/known_hosts
RUN virtualenv -q --python="$(which python2)" --always-copy "/home/${USER_NAME}/${VENV}"
RUN /home/${USER_NAME}/${VENV}/bin/pip install -U "pip<20" setuptools
COPY --chown=${USER_NAME}:${GROUP_NAME} .profile.khan .profile.khan
COPY --chown=${USER_NAME}:${GROUP_NAME} .bash_profile.khan .bash_profile.khan
COPY --chown=${USER_NAME}:${GROUP_NAME} .bashrc.khan .bashrc.khan
COPY --chown=${USER_NAME}:${GROUP_NAME} bash_profile.default .bash_profile
COPY --chown=${USER_NAME}:${GROUP_NAME} profile.default .profile
COPY --chown=${USER_NAME}:${GROUP_NAME} git-completion.bash git-completion.bash
COPY --chown=${USER_NAME}:${GROUP_NAME} .gitconfig.khan .gitconfig
COPY --chown=${USER_NAME}:${GROUP_NAME} . /home/${USER_NAME}/khan/devtools/khan-dotfiles/

COPY --chown=${USER_NAME}:${GROUP_NAME} bashrc.default bashrc.default
RUN cat bashrc.default .bashrc > .bashrc.new
RUN mv .bashrc.new .bashrc
RUN rm -f bashrc.default

RUN mkdir -p /tmp/rust
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs --output /tmp/rust/rustup-init.sh
RUN bash /tmp/rust/rustup-init.sh -y --profile default --no-modify-path
RUN rm -rf /tmp/rust

CMD tail -f /dev/null
4 changes: 4 additions & 0 deletions containers/kabuild/Dockerfile.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.git
containers/kabuild/k8s*.yaml
containers/kabuild/Makefile
*.md
71 changes: 71 additions & 0 deletions containers/kabuild/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
REPO=gcr.io/khan-internal-services/services
IMAGE=${REPO}/kabuild-image
CURRENT_IMAGE_TAG:=$(shell skaffold build -q --dry-run | jq '.builds[0].tag' | tr -d '"')
ifeq (${VER},)
CLEAN_IMAGE_TAG:=$(shell echo ${CURRENT_IMAGE_TAG} | sed 's/-dirty//')
else
CLEAN_IMAGE_TAG:=${IMAGE}:${VER}
endif
ifneq (${CURRENT_IMAGE_TAG},${CLEAN_IMAGE_TAG})
PULL_POLICY=always
else
PULL_POLICY=missing
endif

KAROOT=~/khan

.PHONY: help
help:
@echo " build: Build the container with cloudbuild"
@echo " docker_build: Build the container locally with docker"
@echo " repo_list: List the images we've pushed so far"
@echo " run_dirty: Run the container most recently built (for debugging changes)"
@echo " run: Run what we think is the most recent version of the container"
@echo " run VER=...: Run a specific version of the container"
@echo " current_tag: The current tag that is/was built"
@echo " current image: Full image that is/was built"

# The current image name (and tag)
.PHONY: current_image
current_image:
@echo ${CURRENT_IMAGE_TAG}

# The current tag that will be built
.PHONY: current_tag
current_tag:
@echo ${CURRENT_IMAGE_TAG} | sed 's/.*://'

# Run what we think is the most recent non-dirty image (most recent tag)
.PHONY: run
run:
@echo "Running ${CLEAN_IMAGE_TAG}"
@echo "Mapping private key ~/.ssh/id_rsa"
@echo "Mapping ~/khan"
@docker run --rm -it \
-v ~/.ssh/id_rsa:/home/kabuild/.ssh/id_rsa \
-v ${KAROOT}:/home/kabuild/khan \
${CLEAN_IMAGE_TAG} bash -l

# Run the current image whether dirty or clean
# This is the command to use when testing changes
.PHONY: run_dirty
run_dirty:
@echo "Running ${CURRENT_IMAGE_TAG}"
@echo "Mapping private key ~/.ssh/id_rsa"
@echo "Mapping ~/khan"
@docker run -it --rm --pull ${PULL_POLICY} \
-v ~/.ssh/id_rsa:/home/kabuild/.ssh/id_rsa \
-v ${KAROOT}:/home/kabuild/khan \
${CURRENT_IMAGE_TAG} bash -l

.PHONY: docker_build
docker_build:
skaffold build

.PHONY: build
build:
skaffold build -p cloudbuild

.PHONY: repo_list
repo_list:
gcloud container images list-tags ${IMAGE}
58 changes: 58 additions & 0 deletions containers/kabuild/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# kabuild-image

## Overview

This is an experimental image meant to replace the "base" image in webapp.
It is purely ubuntu 22.04 with the goal to encapsulate all developer tooling
required to run webapp's "make deps". As tooling required to do that is
defined mostly in khan-dotfiles, this is where we are defining the image.

## Versioning

We are using semantic versioning starting with 1.0.0. Versions are created
by a developer manually creating a git-tag containing the semantic version.

## Why skaffold

skaffold is a framework for building and deployment that is a bit opionated.
When using defaults (which aren't necessarily exactly the way Khan typically
does things), it also simplifies aspects of build and deployment. Thus, we
are attempting to use skaffold instead of calling docker (or even make in
most cases).

The only skaffold command we're really using here is "skaffold build"

## Why NOT linux-setup.sh

linux-setup.sh & linux-functions.sh could possibly be used instead of
doing things explicity in the Dockerfile. But that was tried and there was
quite a bit to debug. And in the long term, DevOps anticipates that all
services are built with relatively simple containers instead of a mega
kabuild kitchen-sink container like this one. Thus, the effort to debug
scripts is minimal value. Others are welcome to put in the effort.

## Building

To build locally, run: skaffold build

To build remotely, run: skaffold build -p cloudbuild

## Tagging and building releases

Once pushed to master, a developer should tag that version of master
with an appropriate semantic version (i.e. 1.3.5), push the tag and
then run "make build" locally. That will build the semantic version
and push it to the container repository.

## Other Usage

Please see "make help"

## Remote Use

TBD - i.e. Using "skaffold run" with an appropriate persistent
volume, claim and appropriate ssh-key mapping outght to make remote
development in kubernetes quite realistic. However, ideally this would
be bootstrapped with another container that has the most recent webapp
repo and run "make deps", etc. (This may be somewhat trivial with a
webhook or nightly task.)
26 changes: 26 additions & 0 deletions containers/kabuild/k8s-pvc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: khandev-claim
spec:
storageClassName: khandev-ssd-retain
resources:
requests:
storage: 100Gi
accessModes:
- ReadWriteOnce
---
# This storage class overrides standard-rwo to force Retain=true
# Thus, data sticks around.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: khandev-ssd-retain
provisioner: kubernetes.io/gce-pd
volumeBindingMode: Immediate
allowVolumeExpansion: true
reclaimPolicy: Retain
parameters:
type: pd-ssd
fstype: ext4
replication-type: none
23 changes: 23 additions & 0 deletions containers/kabuild/k8s.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
apiVersion: v1
kind: Pod
metadata:
name: &service-name khandev
spec:
volumes:
- name: khandev-volume
persistentVolumeClaim:
claimName: khandev-claim
containers:
- name: *service-name
image: kabuild-image
volumeMounts:
- mountPath: "/home/khandev/khan"
name: khandev-volume
resources:
requests:
memory: 512Mi
cpu: 250m
limits:
memory: 4096Mi
cpu: 1000m
restartPolicy: Never
26 changes: 26 additions & 0 deletions containers/kabuild/skaffold.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
apiVersion: skaffold/v2beta29
kind: Config
metadata:
name: kabuild
build:
artifacts:
- image: kabuild-image
docker:
dockerfile: containers/kabuild/Dockerfile
context: ../..
tagPolicy:
gitCommit: {}
deploy:
kubectl:
manifests:
- k8s.yaml
portForward:
- resourceType: Pod
port: 8088
- resourceType: Pod
port: 2000
profiles:
- name: cloudbuild
build:
googleCloudBuild:
projectId: khan-internal-services
6 changes: 6 additions & 0 deletions skaffold.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: skaffold/v2beta29
kind: Config
metadata:
name: dotfiles
requires:
- path: containers/kabuild

0 comments on commit 17a9920

Please sign in to comment.