Enable Multiarch docker builds

pull/435/head
Amir Zarrinkafsh 2019-11-07 11:59:24 +11:00 committed by Clément Michaud
parent b9fea361c9
commit 6380bd32d7
10 changed files with 291 additions and 47 deletions

2
.gitignore vendored
View File

@ -42,3 +42,5 @@ users_database.test.yml
.idea .idea
.authelia-interrupt .authelia-interrupt
qemu-*-static

View File

@ -1,12 +1,22 @@
language: go language: go
required: sudo required: sudo
go: go:
- '1.13' - '1.13'
services: services:
- docker - docker
- ntp - ntp
- xvfb - xvfb
addons:
before_script:
- export PATH=./cmd/authelia-scripts/:/tmp:$PATH
jobs:
include:
- stage: test
addons:
chrome: stable chrome: stable
apt: apt:
sources: sources:
@ -14,17 +24,35 @@ addons:
packages: packages:
- libgif-dev - libgif-dev
- google-chrome-stable - google-chrome-stable
script:
before_script: - curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
- curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash - nvm install v11 && nvm use v11 && npm i
- nvm install v11 && nvm use v11 && npm i - source bootstrap.sh
script: - authelia-scripts ci
- "source bootstrap.sh" # TODO(c.michaud): publish built artifact on Github.
- "authelia-scripts ci" - &build-images
after_success: stage: build images
- "authelia-scripts docker publish" env:
- ARCH=amd64
# TODO(c.michaud): publish built artifact on Github. install: skip
script:
- while sleep 9m; do echo '===== Prevent build from terminating ====='; done &
- authelia-scripts docker build --arch=$ARCH
- kill %1
after_success:
- authelia-scripts docker push-image --arch=$ARCH
- <<: *build-images
env:
- ARCH=arm32v7
- <<: *build-images
env:
- ARCH=arm64v8
- stage: deploy manifests
env:
- DOCKER_CLI_EXPERIMENTAL=enabled
install: skip
script:
- authelia-scripts docker push-manifest
notifications: notifications:
email: email:

View File

@ -10,7 +10,7 @@ WORKDIR /go/src/app
COPY . . COPY . .
# CGO_ENABLED=1 is mandatory for building go-sqlite3 # CGO_ENABLED=1 is mandatory for building go-sqlite3
RUN cd cmd/authelia && GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -o authelia RUN cd cmd/authelia && GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -tags netgo -ldflags '-w' -o authelia
# ======================================== # ========================================

48
Dockerfile.arm32v7 100644
View File

@ -0,0 +1,48 @@
# =======================================
# ===== Build image for the backend =====
# =======================================
FROM arm32v7/golang:1.13-alpine AS builder-backend
# qemu binary, gcc and musl-dev are required for building go-sqlite3
COPY ./qemu-arm-static /usr/bin/qemu-arm-static
RUN apk --no-cache add gcc musl-dev
WORKDIR /go/src/app
COPY . .
# CGO_ENABLED=1 is mandatory for building go-sqlite3
RUN cd cmd/authelia && GOOS=linux GOARCH=arm CGO_ENABLED=1 go build -tags netgo -ldflags '-w' -o authelia
# ========================================
# ===== Build image for the frontend =====
# ========================================
FROM node:11-alpine AS builder-frontend
WORKDIR /node/src/app
COPY client .
# Install the dependencies and build
RUN npm ci && npm run build
# ===================================
# ===== Authelia official image =====
# ===================================
FROM arm32v7/alpine:3.10.3
COPY ./qemu-arm-static /usr/bin/qemu-arm-static
RUN apk --no-cache add ca-certificates tzdata && \
rm /usr/bin/qemu-arm-static
WORKDIR /usr/app
COPY --from=builder-backend /go/src/app/cmd/authelia/authelia authelia
COPY --from=builder-frontend /node/src/app/build public_html
EXPOSE 9091
VOLUME /etc/authelia
VOLUME /var/lib/authelia
CMD ["./authelia", "-config", "/etc/authelia/config.yml"]

48
Dockerfile.arm64v8 100644
View File

@ -0,0 +1,48 @@
# =======================================
# ===== Build image for the backend =====
# =======================================
FROM arm64v8/golang:1.13-alpine AS builder-backend
# qemu binary, gcc and musl-dev are required for building go-sqlite3
COPY ./qemu-aarch64-static /usr/bin/qemu-aarch64-static
RUN apk --no-cache add gcc musl-dev
WORKDIR /go/src/app
COPY . .
# CGO_ENABLED=1 is mandatory for building go-sqlite3
RUN cd cmd/authelia && GOOS=linux GOARCH=arm64 CGO_ENABLED=1 go build -tags netgo -ldflags '-w' -o authelia
# ========================================
# ===== Build image for the frontend =====
# ========================================
FROM node:11-alpine AS builder-frontend
WORKDIR /node/src/app
COPY client .
# Install the dependencies and build
RUN npm ci && npm run build
# ===================================
# ===== Authelia official image =====
# ===================================
FROM arm64v8/alpine:3.10.3
COPY ./qemu-aarch64-static /usr/bin/qemu-aarch64-static
RUN apk --no-cache add ca-certificates tzdata && \
rm /usr/bin/qemu-aarch64-static
WORKDIR /usr/app
COPY --from=builder-backend /go/src/app/cmd/authelia/authelia authelia
COPY --from=builder-frontend /node/src/app/build public_html
EXPOSE 9091
VOLUME /etc/authelia
VOLUME /var/lib/authelia
CMD ["./authelia", "-config", "/etc/authelia/config.yml"]

View File

@ -94,7 +94,7 @@ func shell(cmd string) {
runCommand("bash", "-c", cmd) runCommand("bash", "-c", cmd)
} }
func buildDockerImages() { func buildHelperDockerImages() {
shell("docker build -t authelia-example-backend example/compose/nginx/backend") shell("docker build -t authelia-example-backend example/compose/nginx/backend")
shell("docker build -t authelia-duo-api example/compose/duo-api") shell("docker build -t authelia-duo-api example/compose/duo-api")
} }
@ -210,8 +210,8 @@ func Bootstrap(cobraCmd *cobra.Command, args []string) {
installClientNpmPackages() installClientNpmPackages()
bootstrapPrintln("Building development Docker images...") bootstrapPrintln("Building development Docker images...")
buildDockerImages() buildHelperDockerImages()
DockerBuildOfficialImage() dockerBuildOfficialImage(defaultArch)
bootstrapPrintln("Installing Kubernetes dependencies for testing in /tmp... (no junk installed on host)") bootstrapPrintln("Installing Kubernetes dependencies for testing in /tmp... (no junk installed on host)")
installKubernetesDependencies() installKubernetesDependencies()

View File

@ -41,14 +41,6 @@ func RunCI(cmd *cobra.Command, args []string) {
panic(err) panic(err)
} }
fmt.Println("===== Docker image build stage =====")
command = CommandWithStdout("authelia-scripts", "docker", "build")
err = command.Run()
if err != nil {
panic(err)
}
fmt.Println("===== End-to-end testing stage =====") fmt.Println("===== End-to-end testing stage =====")
command = CommandWithStdout("authelia-scripts", "suites", "test", "--headless", "--only-forbidden") command = CommandWithStdout("authelia-scripts", "suites", "test", "--headless", "--only-forbidden")
err = command.Run() err = command.Run()

View File

@ -5,13 +5,67 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func DockerBuildOfficialImage() error { var arch string
var supportedArch = []string{"amd64", "arm32v7", "arm64v8"}
var defaultArch = "amd64"
func init() {
DockerBuildCmd.PersistentFlags().StringVar(&arch, "arch", defaultArch, "target architecture among: "+strings.Join(supportedArch, ", "))
DockerPushCmd.PersistentFlags().StringVar(&arch, "arch", defaultArch, "target architecture among: "+strings.Join(supportedArch, ", "))
}
func checkArchIsSupported(arch string) {
for _, a := range supportedArch {
if arch == a {
return
}
}
log.Fatal("Architecture is not supported. Please select one of " + strings.Join(supportedArch, ", ") + ".")
}
func dockerBuildOfficialImage(arch string) error {
docker := &Docker{} docker := &Docker{}
return docker.Build(IntermediateDockerImageName, ".") // Set default Architecture Dockerfile to amd64
dockerfile := "Dockerfile"
// If not the default value
if arch != defaultArch {
dockerfile = fmt.Sprintf("%s.%s", dockerfile, arch)
}
if arch == "arm32v7" {
err := CommandWithStdout("docker", "run", "--rm", "--privileged", "multiarch/qemu-user-static", "--reset", "-p", "yes").Run()
if err != nil {
panic(err)
}
err = CommandWithStdout("bash", "-c", "wget https://github.com/multiarch/qemu-user-static/releases/download/v4.1.0-1/qemu-arm-static -O ./qemu-arm-static && chmod +x ./qemu-arm-static").Run()
if err != nil {
panic(err)
}
} else if arch == "arm64v8" {
err := CommandWithStdout("docker", "run", "--rm", "--privileged", "multiarch/qemu-user-static", "--reset", "-p", "yes").Run()
if err != nil {
panic(err)
}
err = CommandWithStdout("bash", "-c", "wget https://github.com/multiarch/qemu-user-static/releases/download/v4.1.0-1/qemu-aarch64-static -O ./qemu-aarch64-static && chmod +x ./qemu-aarch64-static").Run()
if err != nil {
panic(err)
}
}
return docker.Build(IntermediateDockerImageName, dockerfile, ".")
} }
// DockerBuildCmd Command for building docker image of Authelia. // DockerBuildCmd Command for building docker image of Authelia.
@ -19,7 +73,8 @@ var DockerBuildCmd = &cobra.Command{
Use: "build", Use: "build",
Short: "Build the docker image of Authelia", Short: "Build the docker image of Authelia",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
err := DockerBuildOfficialImage() checkArchIsSupported(arch)
err := dockerBuildOfficialImage(arch)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -36,10 +91,20 @@ var DockerBuildCmd = &cobra.Command{
// DockerPushCmd Command for pushing Authelia docker image to Dockerhub // DockerPushCmd Command for pushing Authelia docker image to Dockerhub
var DockerPushCmd = &cobra.Command{ var DockerPushCmd = &cobra.Command{
Use: "publish", Use: "push-image",
Short: "Publish Authelia docker image to Dockerhub", Short: "Publish Authelia docker image to Dockerhub",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
publishDockerImage() checkArchIsSupported(arch)
publishDockerImage(arch)
},
}
// DockerManifestCmd Command for pushing Authelia docker manifest to Dockerhub
var DockerManifestCmd = &cobra.Command{
Use: "push-manifest",
Short: "Publish Authelia docker manifest to Dockerhub",
Run: func(cmd *cobra.Command, args []string) {
publishDockerManifest()
}, },
} }
@ -76,14 +141,33 @@ func deploy(docker *Docker, tag string) {
panic(err) panic(err)
} }
docker.Push(imageWithTag) err = docker.Push(imageWithTag)
if err != nil { if err != nil {
panic(err) panic(err)
} }
} }
func publishDockerImage() { func deployManifest(docker *Docker, tag string, amd64tag string, arm32v7tag string, arm64v8tag string) {
imageWithTag := DockerImageName + ":" + tag
fmt.Println("===================================================")
fmt.Println("Docker manifest " + imageWithTag + " will be deployed on Dockerhub.")
fmt.Println("===================================================")
err := docker.Tag(DockerImageName, imageWithTag)
if err != nil {
panic(err)
}
err = docker.Manifest(imageWithTag, amd64tag, arm32v7tag, arm64v8tag)
if err != nil {
panic(err)
}
}
func publishDockerImage(arch string) {
docker := &Docker{} docker := &Docker{}
travisBranch := os.Getenv("TRAVIS_BRANCH") travisBranch := os.Getenv("TRAVIS_BRANCH")
@ -92,12 +176,31 @@ func publishDockerImage() {
if travisBranch == "master" && travisPullRequest == "false" { if travisBranch == "master" && travisPullRequest == "false" {
login(docker) login(docker)
deploy(docker, "master") deploy(docker, "master-"+arch)
} else if travisTag != "" { } else if travisTag != "" {
login(docker) login(docker)
deploy(docker, travisTag) deploy(docker, travisTag+"-"+arch)
deploy(docker, "latest") deploy(docker, "latest-"+arch)
} else { } else {
fmt.Println("Docker image will not be built") fmt.Println("Docker image will not be published")
}
}
func publishDockerManifest() {
docker := &Docker{}
travisBranch := os.Getenv("TRAVIS_BRANCH")
travisPullRequest := os.Getenv("TRAVIS_PULL_REQUEST")
travisTag := os.Getenv("TRAVIS_TAG")
if travisBranch == "master" && travisPullRequest == "false" {
login(docker)
deployManifest(docker, "master", "master-amd64", "master-arm32v7", "master-arm64v8")
} else if travisTag != "" {
login(docker)
deployManifest(docker, travisTag, travisTag+"-amd64", travisTag+"-arm32v7", travisTag+"-arm64v8")
deployManifest(docker, "latest", "latest-amd64", "latest-arm32v7", "latest-arm64v8")
} else {
fmt.Println("Docker manifest will not be published")
} }
} }

View File

@ -4,8 +4,8 @@ package main
type Docker struct{} type Docker struct{}
// Build build a docker image // Build build a docker image
func (d *Docker) Build(tag string, target string) error { func (d *Docker) Build(tag string, dockerfile string, target string) error {
return CommandWithStdout("docker", "build", "-t", tag, target).Run() return CommandWithStdout("docker", "build", "-t", tag, "-f", dockerfile, target).Run()
} }
// Tag tag a docker image. // Tag tag a docker image.
@ -22,3 +22,26 @@ func (d *Docker) Login(username, password string) error {
func (d *Docker) Push(tag string) error { func (d *Docker) Push(tag string) error {
return CommandWithStdout("docker", "push", tag).Run() return CommandWithStdout("docker", "push", tag).Run()
} }
// Manifest push a docker manifest to dockerhub.
func (d *Docker) Manifest(tag string, amd64tag string, arm32v7tag string, arm64v8tag string) error {
err := CommandWithStdout("docker", "manifest", "create", tag, amd64tag, arm32v7tag, arm64v8tag).Run()
if err != nil {
panic(err)
}
err = CommandWithStdout("docker", "manifest", "annotate", tag, arm32v7tag, "--os", "linux", "--arch", "arm").Run()
if err != nil {
panic(err)
}
err = CommandWithStdout("docker", "manifest", "annotate", tag, arm64v8tag, "--os", "linux", "--arch", "arm64", "--variant", "v8").Run()
if err != nil {
panic(err)
}
return CommandWithStdout("docker", "manifest", "push", "--purge", tag).Run()
}

View File

@ -42,7 +42,7 @@ var Commands = []AutheliaCommandDefinition{
AutheliaCommandDefinition{ AutheliaCommandDefinition{
Name: "docker", Name: "docker",
Short: "Commands related to building and publishing docker image", Short: "Commands related to building and publishing docker image",
SubCommands: CobraCommands{DockerBuildCmd, DockerPushCmd}, SubCommands: CobraCommands{DockerBuildCmd, DockerPushCmd, DockerManifestCmd},
}, },
AutheliaCommandDefinition{ AutheliaCommandDefinition{
Name: "hash-password [password]", Name: "hash-password [password]",