Get container running on kubernetes / tilt

This commit is contained in:
Luke Van Seters 2021-09-03 09:55:21 -04:00
parent a77e2d7671
commit bc25cf1eba
15 changed files with 405 additions and 3 deletions

3
.gitignore vendored
View File

@ -16,3 +16,6 @@ htmlcov
# don't commit cache
cache
# k8s
.helm

View File

@ -6,14 +6,16 @@ RUN pip install -U pip \
ENV PATH="${PATH}:/root/.poetry/bin"
COPY . /app
COPY ./pyproject.toml /app/pyproject.toml
COPY ./poetry.lock /app/poetry.lock
WORKDIR /app/
# poetry uses virtual env by default, turn this off inside container
RUN poetry config virtualenvs.create false && \
poetry install
COPY . /app
# easter eggs 😝
RUN echo "PS1='🕵️:\[\033[1;36m\]\h \[\033[1;34m\]\W\[\033[0;35m\]\[\033[1;36m\]$ \[\033[0m\]'" >> ~/.bashrc
CMD /bin/bash
CMD ["/bin/bash"]

16
Tiltfile Normal file
View File

@ -0,0 +1,16 @@
load('ext://helm_remote', 'helm_remote')
helm_remote("postgresql",
repo_name='bitnami',
repo_url='https://charts.bitnami.com/bitnami',
values=["k8s/postgresql/values_dev.yaml"]
)
docker_build('mev-inspect', '.',
live_update=[
sync('.', '/app'),
run('cd /app && poetry install',
trigger='./pyproject.toml'),
],
)
k8s_yaml("k8s/app.yaml")

28
k8s/app.yaml Normal file
View File

@ -0,0 +1,28 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mev-inspect
labels:
app: mev-inspect
spec:
replicas: 1
selector:
matchLabels:
app: mev-inspect
template:
metadata:
labels:
app: mev-inspect
spec:
containers:
- name: mev-inspect
image: mev-inspect:latest
command: [ "/bin/bash", "-c", "--" ]
args: [ "while true; do sleep 30; done;" ]
livenessProbe:
exec:
command:
- ls
- /
initialDelaySeconds: 20
periodSeconds: 5

View File

@ -0,0 +1,5 @@
global:
postgresql:
postgresqlDatabase: "mev_inspect"
postgresqlUsername: "postgres"
postgresqlPassword: "password"

View File

@ -0,0 +1,14 @@
{
"Extensions": [
{
"Name": "helm_remote",
"ExtensionRegistry": "https://github.com/tilt-dev/tilt-extensions",
"TimeFetched": "2021-09-03T08:56:46.938205-04:00"
},
{
"Name": "global_vars",
"ExtensionRegistry": "https://github.com/tilt-dev/tilt-extensions",
"TimeFetched": "2021-09-03T08:56:48.751933-04:00"
}
]
}

View File

@ -0,0 +1,13 @@
# Git Resource
Author: [Bob Jackman](https://github.com/kogi)
An extension for reading/writing global variable values
## Usage
```python
set_global('foo', some_value)
print(get_global('foo'))
unset_global('foo')
```

View File

@ -0,0 +1,11 @@
def get_global(name):
return os.getenv(_get_env_name(name))
def set_global(name, value):
os.putenv(_get_env_name(name), value)
def unset_global(name):
os.unsetenv(_get_env_name(name))
def _get_env_name(name):
return 'TILT_GLOBAL_VAR_%s' % name.upper()

View File

@ -0,0 +1,41 @@
# Helm Remote
Author: [Bob Jackman](https://github.com/kogi)
Install a remotely hosted Helm chart in a way that it will be properly uninstalled when running `tilt down`
## Usage
#### Install a Remote Chart
```py
load('ext://helm_remote', 'helm_remote')
helm_remote('myChartName')
```
##### Additional Parameters
```
helm_remote(chart, repo_url='', repo_name='', release_name='', namespace='', version='', username='', password='', values=[], set=[])
```
* `chart` ( str ) the name of the chart to install
* `repo_name` ( str ) the name of the repo within which to find the chart (assuming the repo is already added locally)
<br> if omitted, defaults to the same value as `chart_name`
* `repo_url` ( str ) the URL of the repo within which to find the chart (equivalent to `helm repo add <repo_name> <repo_url>`)
* `release_name` (str) - the name of the helm release
<br> if omitted, defaults to the same value as `chart_name`
* `namespace` ( str ) the namespace to deploy the chart to (equivalent to helm's `--namespace <namespace>` flags)
* `version` ( str ) the version of the chart to install. If omitted, defaults to latest version (equivalent to helm's `--version` flag)
* `username` ( str ) repository authentication username, if needed (equivalent to helm's `--username` flag)
* `password` ( str ) repository authentication password, if needed (equivalent to helm's `--password` flag)
* `values` ( Union [ str , List [ str ]]) Specify one or more values files (in addition to the values.yaml file in the chart). Equivalent to the helm's `--values` or `-f` flags
* `set` ( Union [ str , List [ str ]]) Directly specify one or more values (equivalent to helm's `--set` flag)
* `allow_duplicates` ( bool ) - Allow duplicate resources. Usually duplicate resources indicate a programmer error.
But some charts specify resources twice.
* `create_namespace` ( bool ) - Create the namespace specified in `namespace` ( equivalent to helm's `--create_namespace` flag)
#### Change the Cache Location
By default `helm_remote` will store retrieved helm charts in the `.helm` directory at your workspace's root.
This location can be customized by calling `os.putenv('TILT_HELM_REMOTE_CACHE_DIR', new_directory)` before loading the module.

View File

@ -0,0 +1,214 @@
load('ext://global_vars', 'get_global', 'set_global')
def _get_skip():
return get_global(_get_skip_name())
def _set_skip(value):
set_global(_get_skip_name(), value)
def _get_skip_name():
return 'HELM_REMOTE_SKIP_UPDATES'
if _get_skip() == None:
_set_skip('False') # Gets set to true after the first update, preventing further updates within the same build/instance/up
def _find_root_tiltfile_dir():
# Find top-level Tilt path
current = os.path.abspath('./')
while current != '/':
if os.path.exists(os.path.join(current, 'tilt_modules')):
return current
current = os.path.dirname(current)
fail('Could not find root Tiltfile')
def _find_cache_dir():
from_env = os.getenv('TILT_HELM_REMOTE_CACHE_DIR', '')
if from_env != '':
return from_env
return os.path.join(_find_root_tiltfile_dir(), '.helm')
# this is the root directory into which remote helm charts will be pulled/cloned/untar'd
# use `os.putenv('TILT_HELM_REMOTE_CACHE_DIR', new_dir)` to change
helm_remote_cache_dir = _find_cache_dir()
watch_settings(ignore=helm_remote_cache_dir)
# TODO: =====================================
# if it ever becomes possible for loaded files to also load their own extensions
# this method can be replaced by `load('ext://namespace', 'namespace_create')
def namespace_create(name):
"""Returns YAML for a namespace
Args: name: The namespace name. Currently not validated.
"""
k8s_yaml(blob("""apiVersion: v1
kind: Namespace
metadata:
name: %s
""" % name))
# TODO: end TODO
# =====================================
def helm_remote(chart, repo_url='', repo_name='', release_name='', values=[], set=[], namespace='', version='', username='', password='', allow_duplicates=False, create_namespace=False):
# ======== Helper methods
def get_local_repo(repo_name, repo_url):
# if no repos are present, helm exit code is >0 and stderr output buffered
added_helm_repos = decode_yaml(local('helm repo list --output yaml 2>/dev/null || true', command_bat='helm repo list --output yaml 2> nul || ver>nul', quiet=True))
repo = [item for item in added_helm_repos if item['name'] == chart and item['url'] == repo_url] if added_helm_repos != None else []
return repo[0] if len(repo) > 0 else None
# Command string builder with common argument logic
def build_helm_command(command, auth=None, version=None):
command = 'helm ' + command
if auth != None:
username, password = auth
if username != '':
command += ' --username %s' % shlex.quote(username)
if password != '':
command += ' --password %s' % shlex.quote(password)
if version != None and version != 'latest':
command += ' --version %s' % shlex.quote(version)
return command
def fetch_chart_details(chart, repo_name, auth, version):
command = build_helm_command('search repo %s/%s --output yaml' % (shlex.quote(repo_name), shlex.quote(chart)), None, version)
results = decode_yaml(local(command, quiet=True))
return results[0] if len(results) > 0 else None
# ======== Condition Incoming Arguments
if repo_name == '':
repo_name = chart
if release_name == '':
release_name = chart
if namespace == '':
namespace = 'default'
if version == '':
version = 'latest'
# ======== Validate before we start trusting chart/repo names
# Based on helm chart conventions, and the fact we don't want anyone traversing directories
# validate is to essentially ensure there's no special characters aside from '-' being used
# str.isalnum accepts dots, which is only dangerous when slashes are allowed
# https://helm.sh/docs/chart_best_practices/conventions/#chart-names
if chart.replace('-', '').isalnum() == False or chart != chart.replace('.', ''):
# https://helm.sh/docs/chart_best_practices/conventions/#chart-names
fail('Chart name is not valid')
if repo_name != chart and repo_name.replace('-', '').isalnum() == False or repo_name != repo_name.replace('.', ''):
# https://helm.sh/docs/chart_best_practices/conventions/#chart-names
fail('Repo name is not valid')
if version != 'latest' and version != version.replace('/', '').replace('\\', ''):
fail('Version cannot contain a forward slash')
# ======== Determine state of existing helm repo
if repo_url != '':
local_helm_repo = get_local_repo(repo_name, repo_url)
if local_helm_repo == None:
# Unaware of repo, add it
repo_command = 'repo add %s %s' % (shlex.quote(repo_name), shlex.quote(repo_url))
# Add authentication for adding the repository if credentials are provided
output = str(local(build_helm_command(repo_command, (username, password)), quiet=True)).rstrip('\n')
if 'already exists' not in output: # repo was added
_set_skip('False')
else:
# Helm is already aware of the chart, update repo (unfortunately you cannot specify a single repo)
if _get_skip() != 'True':
repo_command = 'repo update'
local(build_helm_command(repo_command), quiet=True)
_set_skip('True')
# ======== Create Namespace
if create_namespace and namespace != '' and namespace != 'default':
# avoid a namespace not found error
namespace_create(namespace) # do this early so it manages to register before we attempt to install into it
# ======== Initialize
# -------- targets
pull_target = os.path.join(helm_remote_cache_dir, repo_name, version)
chart_target = os.path.join(pull_target, chart)
cached_chart_exists = os.path.exists(chart_target)
needs_pull = True
if cached_chart_exists:
# Helm chart structure is concrete, we can trust this YAML file to exist
cached_chart_details = read_yaml(os.path.join(chart_target, 'Chart.yaml'))
# check if our local cached chart matches latest remote
remote_chart_details = fetch_chart_details(chart, repo_name, (username, password), version)
# pull when version mismatch
needs_pull = cached_chart_details['version'] != remote_chart_details['version']
if needs_pull:
# -------- commands
pull_command = 'pull %s/%s --untar --destination %s' % (repo_name, chart, pull_target)
# ======== Perform Installation
if cached_chart_exists:
local('rm -rf %s' % chart_target, command_bat='if exist %s ( rd /s /q %s )' % (chart_target, chart_target), quiet=True)
local(build_helm_command(pull_command, (username, password), version), quiet=True)
install_crds(chart, chart_target)
# TODO: since neither `k8s_yaml()` nor `helm()` accept resource_deps,
# sometimes the crds haven't yet finished installing before the below tries
# to run
yaml = helm(chart_target, name=release_name, namespace=namespace, values=values, set=set)
# The allow_duplicates API is only available in 0.17.1+
if allow_duplicates and _version_tuple() >= [0, 17, 1]:
k8s_yaml(yaml, allow_duplicates=allow_duplicates)
else:
k8s_yaml(yaml)
return yaml
def _version_tuple():
ver_string = str(local('tilt version', quiet=True))
versions = ver_string.split(', ')
# pull first string and remove the `v` and `-dev`
version = versions[0].replace('-dev', '').replace('v', '')
return [int(str_num) for str_num in version.split(".")]
# install CRDs as a separate resource and wait for them to be ready
def install_crds(name, directory):
name += '-crds'
files = str(local(r"grep --include='*.yaml' --include='*.yml' -rEil '\bkind[^\w]+CustomResourceDefinition\s*$' %s || exit 0" % directory, quiet=True)).rstrip('\n')
if files == '':
files = []
else:
files = files.split("\n")
# we're applying CRDs directly and not using helm preprocessing
# this will cause errors!
# since installing CRDs in this function is a nice-to-have, just skip
# any that have preprocessing
files = [f for f in files if str(read_file(f)).find('{{') == -1]
if len(files) != 0:
local_resource(name+'-install', cmd='kubectl apply -f %s' % " -f ".join(files), deps=files) # we can wait/depend on this, but it won't cause a proper uninstall
k8s_yaml(files) # this will cause a proper uninstall, but we can't wait/depend on it
# TODO: Figure out how to avoid another named resource showing up in the tilt HUD for this waiter
local_resource(name+'-ready', resource_deps=[name+'-install'], cmd='kubectl wait --for=condition=Established crd --all') # now we can wait for those crds to finish establishing

View File

@ -0,0 +1,7 @@
FROM alpine
RUN apk update && apk add expect busybox-extras
ADD ./verify.exp ./verify.exp
ENTRYPOINT expect < verify.exp

View File

@ -0,0 +1,17 @@
os.putenv('TILT_HELM_REMOTE_CACHE_DIR', os.path.abspath('./.helm'))
load('../Tiltfile', 'helm_remote')
# Note that .helm is in the .tiltignore!
helm_remote('memcached', repo_url='https://charts.bitnami.com/bitnami')
if not os.path.exists('./.helm/memcached'):
fail('memcached failed to load in the right directory')
# This chart has a bunch of CRDs (including templated CRDs), so we can test the CRD init logic.
helm_remote('gloo', repo_url='https://storage.googleapis.com/solo-public-helm',
# The gloo chart has duplicate resources, see discussion here:
# https://github.com/tilt-dev/tilt/issues/3656
allow_duplicates=True)
docker_build('helm-remote-test-verify', '.')
k8s_yaml('job.yaml')
k8s_resource('helm-remote-test-verify', resource_deps=['memcached'])

View File

@ -0,0 +1,12 @@
apiVersion: batch/v1
kind: Job
metadata:
name: helm-remote-test-verify
spec:
backoffLimit: 1
template:
spec:
containers:
- name: helm-remote-test-verify
image: helm-remote-test-verify
restartPolicy: Never

View File

@ -0,0 +1,7 @@
#!/bin/bash
cd "$(dirname "$0")"
set -ex
tilt ci
tilt down --delete-namespaces

View File

@ -0,0 +1,12 @@
#!/usr/bin/expect
spawn telnet memcached 11211
expect {
timeout {exit 1}
"Connected"
}
send "stats\r\n"
expect {
timeout {exit 1}
"STAT pid 1"
}