docker + makefile =CI Pipeline

1. 简单的docker&makefile

1.1 编辑 dockerfile

$ cat Dockerfile
FROM busybox
CMD ["date"]

1.2 编辑 Makefile

build:
    docker build -t benhall/docker-make-example .

run:
    docker run benhall/docker-make-example

default: build test

1.3 执行 make

#通过dockerfile构建容器
$ make
docker build -t benhall/docker-make-example .
Sending build context to Docker daemon  116.7kB
Step 1/2 : FROM busybox
latest: Pulling from library/busybox
24fb2886d6f6: Pull complete 
Digest: sha256:f7ca5a32c10d51aeda3b4d01c61c6061f497893d7f6628b92f822f7117182a57
Status: Downloaded newer image for busybox:latest
 ---> 16ea53ea7c65
Step 2/2 : CMD ["date"]
 ---> Running in 26d6f36ad3b4
Removing intermediate container 26d6f36ad3b4
 ---> 339b5a708dce
Successfully built 339b5a708dce
Successfully tagged benhall/docker-make-example:latest

#查看构建效果
$ docker images |grep benha
benhall/docker-make-example   latest      339b5a708dce   2 minutes ago    1.24MB

#运行容器
$ make run
docker run benhall/docker-make-example
Mon Sep 27 08:36:15 UTC 2021
$ docker ps |grep ben
$ docker ps -a |grep ben
c8cf9f9b7090   benhall/docker-make-example   "date"                   14 seconds ago   Exited (0) 13 seconds ago             amazing_buck

#一次性构建部署
$ make build run
docker build -t benhall/docker-make-example .
Sending build context to Docker daemon  116.7kB
Step 1/2 : FROM busybox
 ---> 16ea53ea7c65
Step 2/2 : CMD ["date"]
 ---> Using cache
 ---> 339b5a708dce
Successfully built 339b5a708dce
Successfully tagged benhall/docker-make-example:latest
docker run benhall/docker-make-example
Mon Sep 27 08:38:11 UTC 2021

我希望我所有的项目都能像这样工作:

git pull && make test && make build && make deploy

2. docker & Makefile 实战

项目:https://github.com/Ghostwritten/dockerbuild

2.1 编写 Makefile

版权作者说明
# --------------------------------------------------------------------
# Copyright (c) 2019 LINKIT, The Netherlands. All Rights Reserved.
# Author(s): Anthony Potappel
# 
# This software may be modified and distributed under the terms of the
# MIT license. See the LICENSE file for details.
# --------------------------------------------------------------------

# If you see pwd_unknown showing up, this is why. Re-calibrate your system.
#条件赋值 ( ?= ) 如果变量未定义,则使用符号中的值定义变量。如果该变量已经赋值,则该赋值语句无效。
PWD ?= pwd_unknown

# PROJECT_NAME defaults to name of the current directory.
# should not to be changed if you follow GitOps operating procedures.
PROJECT_NAME = $(notdir $(PWD))

# Note. If you change this, you also need to update docker-compose.yml.
# only useful in a setting with multiple services/ makefiles.
#简单赋值 ( := ) 编程语言中常规理解的赋值方式,只对当前语句的变量有效。
SERVICE_TARGET := main

# if vars not set specifially: try default to environment, else fixed value.
# strip to ensure spaces are removed in future editorial mistakes.
# tested to work consistently on popular Linux flavors and Mac.
ifeq ($(user),)
# USER retrieved from env, UID from shell.
HOST_USER ?= $(strip $(if $(USER),$(USER),nodummy))
HOST_UID ?= $(strip $(if $(shell id -u),$(shell id -u),4000))
else
# allow override by adding user= and/ or uid=  (lowercase!).
# uid= defaults to 0 if user= set (i.e. root).
HOST_USER = $(user)
HOST_UID = $(strip $(if $(uid),$(uid),0))
endif

THIS_FILE := $(lastword $(MAKEFILE_LIST))
CMD_ARGUMENTS ?= $(cmd)

# export such that its passed to shell functions for Docker to pick up.
export PROJECT_NAME
export HOST_USER
export HOST_UID

# all our targets are phony (no files to check).
.PHONY: shell help build rebuild service login test clean prune

# suppress makes own output
#.SILENT:

# shell is the first target. So instead of: make shell cmd="whoami", we can type: make cmd="whoami".
# more examples: make shell cmd="whoami && env", make shell cmd="echo hello container space".
# leave the double quotes to prevent commands overflowing in makefile (things like && would break)
# special chars: '',"",|,&&,||,*,^,[], should all work. Except "$" and "`", if someone knows how, please let me know!).
# escaping (\) does work on most chars, except double quotes (if someone knows how, please let me know)
# i.e. works on most cases. For everything else perhaps more useful to upload a script and execute that.
shell:
ifeq ($(CMD_ARGUMENTS),)
    # no command is given, default to shell
    docker-compose -p $(PROJECT_NAME)_$(HOST_UID) run --rm $(SERVICE_TARGET) sh
else
    # run the command
    docker-compose -p $(PROJECT_NAME)_$(HOST_UID) run --rm $(SERVICE_TARGET) sh -c "$(CMD_ARGUMENTS)"
endif

# Regular Makefile part for buildpypi itself
help:
    @echo ''
    @echo 'Usage: make [TARGET] [EXTRA_ARGUMENTS]'
    @echo 'Targets:'
    @echo '  build        build docker --image-- for current user: $(HOST_USER)(uid=$(HOST_UID))'
    @echo '  rebuild      rebuild docker --image-- for current user: $(HOST_USER)(uid=$(HOST_UID))'
    @echo '  test         test docker --container-- for current user: $(HOST_USER)(uid=$(HOST_UID))'
    @echo '  service       run as service --container-- for current user: $(HOST_USER)(uid=$(HOST_UID))'
    @echo '  login       run as service and login --container-- for current user: $(HOST_USER)(uid=$(HOST_UID))'
    @echo '  clean        remove docker --image-- for current user: $(HOST_USER)(uid=$(HOST_UID))'
    @echo '  prune        shortcut for docker system prune -af. Cleanup inactive containers and cache.'
    @echo '  shell      run docker --container-- for current user: $(HOST_USER)(uid=$(HOST_UID))'
    @echo ''
    @echo 'Extra arguments:'
    @echo 'cmd=:    make cmd="whoami"'
    @echo '# user= and uid= allows to override current user. Might require additional privileges.'
    @echo 'user=:    make shell user=root (no need to set uid=0)'
    @echo 'uid=:    make shell user=dummy uid=4000 (defaults to 0 if user= set)'

rebuild:
    # force a rebuild by passing --no-cache
    docker-compose build --no-cache $(SERVICE_TARGET)

service:
    # run as a (background) service
    docker-compose -p $(PROJECT_NAME)_$(HOST_UID) up -d $(SERVICE_TARGET)

login: service
    # run as a service and attach to it
    docker exec -it $(PROJECT_NAME)_$(HOST_UID) sh

build:
    # only build the container. Note, docker does this also if you apply other targets.
    docker-compose build $(SERVICE_TARGET)

clean:
    # remove created images
    @docker-compose -p $(PROJECT_NAME)_$(HOST_UID) down --remove-orphans --rmi all 2>/dev/null \
    && echo 'Image(s) for "$(PROJECT_NAME):$(HOST_USER)" removed.' \
    || echo 'Image(s) for "$(PROJECT_NAME):$(HOST_USER)" already removed.'

prune:
    # clean all that is not actively used
    docker system prune -af

test:
    # here it is useful to add your own customised tests
    docker-compose -p $(PROJECT_NAME)_$(HOST_UID) run --rm $(SERVICE_TARGET) sh -c '\
        echo "I am `whoami`. My uid is `id -u`." && echo "Docker runs!"' \
    && echo success

2.2 处理 UID

未配置 UserID (UID) 时,Docker 会将容器默认为用户 root。当您开始处理生产系统时,需要练习正确配置用户。你可以在Dockers 的最佳实践列表上阅读它,来自 K8s 的这篇文章也很好地涵盖了它。

当您在 Mac 上本地运行测试时,root 会映射到您自己的用户,因此一切正常。生产平台——应该——被配置为用户隔离,但这并不总是默认的。例如,如果您在 Linux 系统上运行测试(没有重新映射),您可能会偶然发现 Docker 生成的文件归 root 所有的问题

当您在一个系统上有多个用户时,您也会遇到问题。即使您不共享一个系统,运行并行测试也同样需要分离。而且,如果没有正确的 UID 设置,您甚至如何传递凭据(例如,通过将卷链接为 ~/.ssh 和 ~/.aws)?后者是处理基础设施部署时的常见模式。 如果你开始成为一个更密集的 docker-consumer,你的团队会成长并且事情会变得复杂,最终你会想要或需要在你的所有项目中嵌入 UID 分离。

幸运的是,尽早配置 UID 是(相对)容易的。虽然我花了一些练习才能获得良好的设置,但我现在有一个模板(假设使用 Makefile)自动完成所有操作,成本几乎为零。 以下是要使用的docker-compose文件的副本:

version: '3.4'
services:
  main:
    # Makefile fills PROJECT_NAME to current directory name.
    # add UID to allow multiple users run this in parallel
    container_name: ${PROJECT_NAME}_${HOST_UID:-4000}
    hostname: ${PROJECT_NAME}
    # These variables are passed into the container.
    environment:
      - UID=${HOST_UID:-4000}
    # Run with user priviliges by default.
    user: ${HOST_USER:-nodummy}
    image: ${PROJECT_NAME}:${HOST_USER:-nodummy}
    build:
      context: .
      # Build for current user.
      target: user
      dockerfile: Dockerfile
      # These variables are passed to Dockerfile.
      args:
        - HOST_UID=${HOST_UID:-4000}
        - HOST_USER=${HOST_USER:-nodummy}
    # Run container as a service. Replace with something useful.
    command: ["tail", "-f", "/dev/null"]
    # Copy current (git-) project into container.
    volumes:
      - ${PWD:-.}:/home/${HOST_USER}/${PROJECT_NAME}

默认变量的一种写法

${HOST_USER:-nodummy} 
${HOST_UID:-4000}

这会从您的运行时复制变量,如果不存在,则分别默认为“nodummy”和“4000”。如果您不喜欢默认值,请执行以下操作:

${HOST_USER:?You forgot to set HOST_USER in .env!}
${HOST_UID:?You forgot to set HOST_UID in .env!}

注意“HOST_”前缀。我避免直接​​使用 USER 和 UID。不保证这些变量在运行时可用。USER 通常在 shell 中可用,但 UID 主要是 Docker 不会获取的环境变量。拥有单独的命名方案可以防止意外,并允许灵活配置自动化管道。 Dockerfile内容如下:

FROM alpine as base

RUN apk update \
    && apk add --no-cache \
        bash

FROM scratch as user
COPY --from=base . .

ARG HOST_UID=${HOST_UID:-4000}
ARG HOST_USER=${HOST_USER:-nodummy}

RUN [ "${HOST_USER}" == "root" ] || \
    (adduser -h /home/${HOST_USER} -D -u ${HOST_UID} ${HOST_USER} \
    && chown -R "${HOST_UID}:${HOST_UID}" /home/${HOST_USER})

USER ${HOST_USER}
WORKDIR /home/${HOST_USER}

在这里,我们构建了一个小型(只有 10MB,微服务 FTW!)Alpine 容器,添加了 bash 以供娱乐和练习。我们应用所谓的分阶段构建的概念来保持基础镜像(可重用构建组件)与用户镜像(为特定运行准备的镜像)分离。

使用 Makefile 时,所有变量都会自动设置。这个 Dockerfile 在没有 Makefile 的情况下也能正常工作,但用户仍然可以在自己的运行时或单独的env-file 中配置变量.

如果您处于开发模式,您可能会遇到某些障碍,需要您以 root 用户身份进行故障排除。

make shell user=root

2.3 执行 make

# download our files
git clone https://github.com/LINKIT-Group/dockerbuild
# enter directory
cd dockerbuild
# build, test and run a command
make build test shell cmd="whoami"
# my favorite for container exploration
make shell
# shell-target is the default (first item), so this also works: 
make cmd="whoami"
make cmd="ls /"
# force a rebuild, test and cleanup
make rebuild test clean

2.4 开发小技巧

  • 运行容器最好加上--rm
  • 我们还可以加其他目标任务,比如将镜像推送到仓库。

参考:

Copyright © ghostwritten 浙ICP备2020032454号 2022 all right reserved,powered by Gitbook该文件修订时间: 2022-12-23 16:26:47

results matching ""

    No results matching ""