diff --git a/.envrc_template b/.envrc_template new file mode 100644 index 0000000..e112a7e --- /dev/null +++ b/.envrc_template @@ -0,0 +1,3 @@ +# Rename to .envrc +export TAILSCALE_AUTH_KEY="" +# export TAILSCALE_HOSTNAME="" \ No newline at end of file diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..7fccbea --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,71 @@ +name: Build Containers + +# Controls when the workflow will run +on: + workflow_dispatch: + push: + branches: + - "main" + - "dev" + - "v*.*.*" + paths: + - "k8s/**" + +permissions: + contents: read + packages: write + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build: + runs-on: ubuntu-latest + steps: + # Get the repositery's code + - name: Checkout + uses: actions/checkout@v2 + + # https://github.com/docker/setup-qemu-action + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + # https://github.com/docker/setup-buildx-action + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to the Container registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v3 + with: + images: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + # generate Docker tags based on the following events/attributes + tags: | + type=schedule + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha + flavor: | + latest=true + + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: images/tailscale/. + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1761c01 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.envrc \ No newline at end of file diff --git a/README.md b/README.md index d91abfa..f5299a4 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,59 @@ # Tailscale in Docker without elevated privileges -See associated blog post: https://asselin.engineer/tailscale-docker +See associated blog post: -**Replace TAILSCALE_AUTH_KEY in `*/tailscale/start.sh` with your own**: https://login.tailscale.com/admin/settings/keys +**Set the TAILSCALE_AUTH_KEY with your own ephemeral auth key**: -## simple-example +The `Makefile` contains all commands to launch the various examples. Refer to it to understand which commands are used. + +## docker-compose + +By default, no state is saved. The nodes are removed from the network when the tailscale container is terminated. This means the ip address is never the same. +The `stateful-example` does save the tailscale node state to a docker volume. + +Usage: +````bash +export TAILSCALE_AUTH_KEY="your-key" +# set which project is used +export PROJECT_DIRECTORY="docker-compose/simple-example" +# Sart with rebuild if necessary: +docker-compose --project-directory=${PROJECT_DIRECTORY} up -d --build +# Show logs and tail (follow): +docker-compose --project-directory=${PROJECT_DIRECTORY} logs --follow +# Stop: +docker-compose --project-directory=${PROJECT_DIRECTORY} down +```` + +### simple-example As explained in the blog post, uses a docker-compose service to add the container in the VPN. -## complex-example +### complex-example Not complex but more complex than the simple-example. -A nginx layer is added. It manages two services in independent containers at locations `/service-one` and `/service-two`. +A nginx layer is added. It manages two services in independent containers at urls `/service-one` and `/service-two`. -## TODO +### stateful-example -- force reuse hostname in tailscale instead of adding suffix. Example: first container is assigned `hostname`. Then, if container is recreated, Tailscale assigns `hostname-1`. Possibly helpful [info](https://tailscale.com/kb/1111/ephemeral-nodes/#can-i-create-an-ephemeral-node-without-an-auth-key). +Same as simple-example but uses a volume to save state. The goal is to be able to reuse the same tailscale hostname _and ip address_. +Useful in situations where the tailscale magic DNS cannot be used. + +## K8S + +Same as the simple-example but on kubernetes. + +Requirements: + +- [Kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installing-with-a-package-manager) +- [Kubectl](https://kubernetes.io/docs/tasks/tools/) + +Usage: +````bash +# Create cluster +kind create cluster --name tailscale +kubectl get nodes +# Deploy tailscale and demo webpage: +kubectl apply -f k8s/simple-example/deployment.yaml +# Delete cluster: +kind delete cluster --name tailscale +```` diff --git a/complex-example/tailscale/Dockerfile b/complex-example/tailscale/Dockerfile deleted file mode 100644 index 29eb6e3..0000000 --- a/complex-example/tailscale/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM tailscale/tailscale:v1.29 -COPY start.sh /usr/bin/start.sh -RUN chmod +x /usr/bin/start.sh -CMD "start.sh" \ No newline at end of file diff --git a/complex-example/tailscale/start.sh b/complex-example/tailscale/start.sh deleted file mode 100644 index 2692224..0000000 --- a/complex-example/tailscale/start.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/ash -echo "Starting TS daemon" -tailscaled --tun=userspace-networking & -PID=$! -until tailscale up --authkey=${TAILSCALE_AUTH_KEY} --hostname=complex-example; do - sleep 0.1 -done -tailscale status -wait ${PID} \ No newline at end of file diff --git a/complex-example/docker-compose.yml b/docker-compose/complex-example/docker-compose.yml similarity index 53% rename from complex-example/docker-compose.yml rename to docker-compose/complex-example/docker-compose.yml index 60f327c..ffb6a91 100644 --- a/complex-example/docker-compose.yml +++ b/docker-compose/complex-example/docker-compose.yml @@ -2,12 +2,14 @@ version: "3.9" services: tailscale: build: - context: ./tailscale + context: images/tailscale environment: - - TAILSCALE_AUTH_KEY + TAILSCALE_AUTH_KEY: ${TAILSCALE_AUTH_KEY:?err} + TAILSCALE_HOSTNAME: ${TAILSCALE_HOSTNAME:-tailscale-docker-complex-example} + TAILSCALE_STATE_ARG: "mem:" nginx: build: - context: ./nginx + context: images/nginx depends_on: - service-one - service-two diff --git a/docker-compose/simple-example/docker-compose.yml b/docker-compose/simple-example/docker-compose.yml new file mode 100644 index 0000000..8693cae --- /dev/null +++ b/docker-compose/simple-example/docker-compose.yml @@ -0,0 +1,12 @@ +version: "3.9" +services: + tailscale: + build: + context: ../../images/tailscale + environment: + TAILSCALE_AUTH_KEY: ${TAILSCALE_AUTH_KEY:?err} + TAILSCALE_HOSTNAME: ${TAILSCALE_HOSTNAME:-tailscale-docker-simple-example} + TAILSCALE_STATE_ARG: "mem:" + some-service-1: + image: nginxdemos/hello + network_mode: "service:tailscale" diff --git a/docker-compose/stateful-example/docker-compose.yml b/docker-compose/stateful-example/docker-compose.yml new file mode 100644 index 0000000..039bade --- /dev/null +++ b/docker-compose/stateful-example/docker-compose.yml @@ -0,0 +1,15 @@ +version: "3.9" +services: + tailscale: + build: + context: ../../images/tailscale + environment: + TAILSCALE_AUTH_KEY: ${TAILSCALE_AUTH_KEY:?err} + TAILSCALE_HOSTNAME: ${TAILSCALE_HOSTNAME:-tailscale-docker-stateful-example} + TAILSCALE_STATE_ARG: "/var/lib/tailscale_state/tailscale.state" # a file + volumes: + # a volume is used but it could be a local directory. + - /var/lib/tailscale_state/ + some-service-1: + image: nginxdemos/hello + network_mode: "service:tailscale" diff --git a/complex-example/nginx/Dockerfile b/images/nginx/Dockerfile similarity index 100% rename from complex-example/nginx/Dockerfile rename to images/nginx/Dockerfile diff --git a/complex-example/nginx/conf.d/default.conf b/images/nginx/conf.d/default.conf similarity index 100% rename from complex-example/nginx/conf.d/default.conf rename to images/nginx/conf.d/default.conf diff --git a/simple-example/tailscale/Dockerfile b/images/tailscale/Dockerfile similarity index 71% rename from simple-example/tailscale/Dockerfile rename to images/tailscale/Dockerfile index 29eb6e3..7869d61 100644 --- a/simple-example/tailscale/Dockerfile +++ b/images/tailscale/Dockerfile @@ -1,4 +1,4 @@ -FROM tailscale/tailscale:v1.29 +FROM tailscale/tailscale:v1.30 COPY start.sh /usr/bin/start.sh RUN chmod +x /usr/bin/start.sh CMD "start.sh" \ No newline at end of file diff --git a/images/tailscale/start.sh b/images/tailscale/start.sh new file mode 100644 index 0000000..73aaaed --- /dev/null +++ b/images/tailscale/start.sh @@ -0,0 +1,12 @@ +#!/bin/ash +trap 'kill -TERM $PID' TERM INT +echo "Starting Tailscale daemon" +# -state=mem: will logout and remove ephemeral node from network immediately after ending. +tailscaled --tun=userspace-networking --state=${TAILSCALE_STATE_ARG} & +PID=$! +until tailscale up --authkey="${TAILSCALE_AUTH_KEY}" --hostname="${TAILSCALE_HOSTNAME}"; do + sleep 0.1 +done +tailscale status +wait ${PID} +wait ${PID} \ No newline at end of file diff --git a/k8s/simple-example/deployment.yaml b/k8s/simple-example/deployment.yaml new file mode 100644 index 0000000..ecc93c1 --- /dev/null +++ b/k8s/simple-example/deployment.yaml @@ -0,0 +1,33 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: tailscale +spec: + selector: + matchLabels: + app: tailscale + template: + metadata: + labels: + app: tailscale + spec: + containers: + - name: tailscale + image: ghcr.io/lpasselin/tailscale-docker:latest + env: + - name: TAILSCALE_AUTH_KEY + value: "${TAILSCALE_AUTH_KEY:-err}" + - name: TAILSCALE_HOSTNAME + value: "tailscale-docker-k8s-simple" + - name: TAILSCALE_STATE_ARG + value: "mem:" + resources: + limits: + memory: "128Mi" + cpu: "500m" + - name: nginx + image: nginxdemos/hello + resources: + limits: + memory: "128Mi" + cpu: "500m" diff --git a/simple-example/docker-compose.yml b/simple-example/docker-compose.yml deleted file mode 100644 index 2634837..0000000 --- a/simple-example/docker-compose.yml +++ /dev/null @@ -1,10 +0,0 @@ -version: "3.9" -services: - tailscale: - build: - context: ./tailscale - environment: - - TAILSCALE_AUTH_KEY - some-service-1: - image: nginxdemos/hello - network_mode: "service:tailscale" diff --git a/simple-example/tailscale/start.sh b/simple-example/tailscale/start.sh deleted file mode 100644 index 2692224..0000000 --- a/simple-example/tailscale/start.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/ash -echo "Starting TS daemon" -tailscaled --tun=userspace-networking & -PID=$! -until tailscale up --authkey=${TAILSCALE_AUTH_KEY} --hostname=complex-example; do - sleep 0.1 -done -tailscale status -wait ${PID} \ No newline at end of file