From 17a9920f2f69b034212d34c0871f7967af87af5a Mon Sep 17 00:00:00 2001 From: Eric Brown Date: Mon, 3 Oct 2022 13:51:23 -0700 Subject: [PATCH] INFRA-8460 Initial kabuild container (#65) 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. --- .gitignore | 1 + containers/kabuild/Dockerfile | 153 +++++++++++++++++++++ containers/kabuild/Dockerfile.dockerignore | 4 + containers/kabuild/Makefile | 71 ++++++++++ containers/kabuild/README.md | 58 ++++++++ containers/kabuild/k8s-pvc.yaml | 26 ++++ containers/kabuild/k8s.yaml | 23 ++++ containers/kabuild/skaffold.yaml | 26 ++++ skaffold.yaml | 6 + 9 files changed, 368 insertions(+) create mode 100644 containers/kabuild/Dockerfile create mode 100644 containers/kabuild/Dockerfile.dockerignore create mode 100644 containers/kabuild/Makefile create mode 100644 containers/kabuild/README.md create mode 100644 containers/kabuild/k8s-pvc.yaml create mode 100644 containers/kabuild/k8s.yaml create mode 100644 containers/kabuild/skaffold.yaml create mode 100644 skaffold.yaml diff --git a/.gitignore b/.gitignore index 8233462..253f82c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /**/packer_cache/ /**/builds/ +.vscode diff --git a/containers/kabuild/Dockerfile b/containers/kabuild/Dockerfile new file mode 100644 index 0000000..edb3c0c --- /dev/null +++ b/containers/kabuild/Dockerfile @@ -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 diff --git a/containers/kabuild/Dockerfile.dockerignore b/containers/kabuild/Dockerfile.dockerignore new file mode 100644 index 0000000..acb5229 --- /dev/null +++ b/containers/kabuild/Dockerfile.dockerignore @@ -0,0 +1,4 @@ +.git +containers/kabuild/k8s*.yaml +containers/kabuild/Makefile +*.md diff --git a/containers/kabuild/Makefile b/containers/kabuild/Makefile new file mode 100644 index 0000000..fcd0939 --- /dev/null +++ b/containers/kabuild/Makefile @@ -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} diff --git a/containers/kabuild/README.md b/containers/kabuild/README.md new file mode 100644 index 0000000..1eb457c --- /dev/null +++ b/containers/kabuild/README.md @@ -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.) diff --git a/containers/kabuild/k8s-pvc.yaml b/containers/kabuild/k8s-pvc.yaml new file mode 100644 index 0000000..8b5b334 --- /dev/null +++ b/containers/kabuild/k8s-pvc.yaml @@ -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 diff --git a/containers/kabuild/k8s.yaml b/containers/kabuild/k8s.yaml new file mode 100644 index 0000000..c71bf2f --- /dev/null +++ b/containers/kabuild/k8s.yaml @@ -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 diff --git a/containers/kabuild/skaffold.yaml b/containers/kabuild/skaffold.yaml new file mode 100644 index 0000000..f10ba45 --- /dev/null +++ b/containers/kabuild/skaffold.yaml @@ -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 diff --git a/skaffold.yaml b/skaffold.yaml new file mode 100644 index 0000000..01a18e9 --- /dev/null +++ b/skaffold.yaml @@ -0,0 +1,6 @@ +apiVersion: skaffold/v2beta29 +kind: Config +metadata: + name: dotfiles +requires: +- path: containers/kabuild