使用docker build构建image
文章目录
- 环境
- 步骤
- 准备
- 例1:基本用法
- 例2:缓存layer
- 例3:Multi-stage
- 例4:Mount
- cache mount
- bind mount
- 例5:参数
- 例6:Export文件
- 例7:测试
- 参考
环境
- RHEL 9.3
- Docker Community 24.0.7
步骤
在Docker的官网上( https://docs.docker.com/build/guide/
),有一个现成的hands-on例子。
准备
首先克隆 buildme
项目:
git clone https://github.com/dockersamples/buildme.git
其结构如下:
➜ buildme git:(main) tree
.
├── chapters
│ ├── 1.Dockerfile
│ ├── 2.Dockerfile
│ ├── 3.Dockerfile
│ ├── 4.Dockerfile
│ ├── 5.Dockerfile
│ ├── 6.Dockerfile
│ ├── 7.Dockerfile
│ └── 8.Dockerfile
├── cmd
│ ├── client
│ │ ├── main.go
│ │ ├── request.go
│ │ └── ui.go
│ └── server
│ ├── main.go
│ └── translate.go
├── Dockerfile
├── go.mod
├── go.sum
├── README.md
└── Taskfile.yml4 directories, 18 files
注: chapters
目录和 Taskfile.yml
文件只是为了方便快速切换 Dockerfile
文件的内容。它使用了 task
工具,这是一个基于Go的构建工具,其安装和用法参见 https://taskfile.dev
。
实际上, task
工具和本例中的Go项目并没有直接关联。对于本例来说,使用该工具只是为了方便把 chapters
目录下的某个Dockerfile文件覆盖到项目的根目录下。具体命令为: task goto:<N>
。若不想用该工具,可以直接无视之。
例1:基本用法
打开 Dockerfile
文件,如下:
# syntax=docker/dockerfile:1
FROM golang:1.20-alpine
WORKDIR /src
COPY . .
RUN go mod download
RUN go build -o /bin/client ./cmd/client
RUN go build -o /bin/server ./cmd/server
ENTRYPOINT [ "/bin/server" ]
基本上,每一行就是一条指令:
-
# syntax=docker/dockerfile:1
这是一个解析器指令(parser directive),指定了Dockerfile的语法版本。 -
FROM golang:1.20-alpine
指定parent image(golang
)和其版本(1.20-alpine
)。 -
WORKDIR /src
容器的工作目录(即操作容器时的当前目录),若目录不存在则会被创建。 -
COPY . .
从宿主机复制文件/目录到容器里。目标地址可以是绝对路径或相对路径,若是相对路径,则以前面指定的工作目录为基础。 -
RUN go mod download
运行命令(下载所需的Go module)。 -
RUN go build -o /bin/client ./cmd/client
同上(构建client程序)。 -
RUN go build -o /bin/server ./cmd/server
同上(构建server程序)。 -
ENTRYPOINT [ "/bin/server" ]
指定当启动容器时运行的命令。本例中就是启动server。
接下来,开始构建:
docker build --tag=buildme .
运行报错了,试了几次都报错,如下:
➜ buildme git:(main) docker build --tag=buildme .
[+] Building 151.9s (10/12) docker:default=> [internal] load .dockerignore 0.0s=> => transferring context: 2B 0.0s=> [internal] load build definition from Dockerfile 0.0s=> => transferring dockerfile: 304B 0.0s=> resolve image config for docker.io/docker/dockerfile:1 0.7s=> CACHED docker-image://docker.io/docker/dockerfile:1@sha256:ac85f380a63b13dfcefa89046420e1781752bab202122f8f50032edf31be0021 0.0s=> [internal] load metadata for docker.io/library/golang:1.20-alpine 0.7s=> [1/6] FROM docker.io/library/golang:1.20-alpine@sha256:3ae92bcab3301033767e8c13d401394e53ad2732f770c313a34630193ed009b8 0.0s=> [internal] load build context 0.0s=> => transferring context: 6.46kB 0.0s=> CACHED [2/6] WORKDIR /src 0.0s=> CACHED [3/6] COPY . . 0.0s=> ERROR [4/6] RUN go mod download 150.3s
------> [4/6] RUN go mod download:
150.3 go: github.com/atotto/clipboard@v0.1.4: Get "https://proxy.golang.org/github.com/atotto/clipboard/@v/v0.1.4.mod": dial tcp 142.251.43.17:443: i/o timeout
------
Dockerfile:5
--------------------3 | WORKDIR /src4 | COPY . .5 | >>> RUN go mod download6 | RUN go build -o /bin/client ./cmd/client7 | RUN go build -o /bin/server ./cmd/server
--------------------
ERROR: failed to solve: process "/bin/sh -c go mod download" did not complete successfully: exit code: 1
注:有些步骤是 CACHED
,是因为运行了好几次,而这些步骤在之前构建时是成功的。关于缓存,详见例2。
从报错信息可以判断处,网站连接有问题,解决方法是设置代理。
编辑 Dockerfile
文件,如下:
......
COPY . .
RUN go env -w GOPROXY=https://goproxy.cn # 添加这一行
RUN go mod download
......
保存,再次运行 docker build --tag=buildme .
,这次成功了。
查看image:
➜ buildme git:(main) ✗ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme latest 773f384bf110 2 minutes ago 416MB
......
接下来,运行容器:
➜ buildme git:(main) ✗ docker run --name=buildme --rm --detach buildme
f1d6e9038ee74d6524fa6c614e7ff133852ab7fd24e59f7c188438949b7bb828
其中:
--rm
:在退出容器时,自动将其删除。--detach
:在后台运行容器。
查看容器:
➜ buildme git:(main) ✗ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f1d6e9038ee7 buildme "/bin/server" 10 seconds ago Up 9 seconds buildme
进入容器:
docker exec -it buildme /bin/client
如下:
> Translate a message...╭─────────────────────────╮│ Hit Enter to translate. │╰─────────────────────────╯Ctrl+C to exit.
输入 hello world
,回车,如下:
> Translate a message...╭───────────────────────────────────────╮│ Input: hello world ││ Translation: hohelollolo wowororloldo │╰───────────────────────────────────────╯Ctrl+C to exit.
测试完毕,按 Ctrl + C
退出。
停止容器:
docker stop buildme
例2:缓存layer
粗略的讲,每一条build指令会转换为一个image layer。
构建时,会尽量重用之前已经构建好的layer。如果一个layer没有修改过,则builder会从build cache里获取,但如果layer有修改,则它和随后的layer都会重新build。
本例中,如果 COPY
指令的源没有发生变化,则再次构建时,会从cache里获取,速度快很多。
➜ buildme git:(main) ✗ docker build --tag=buildme .
[+] Building 3.0s (14/14) FINISHED docker:default=> [internal] load build definition from Dockerfile 0.0s=> => transferring dockerfile: 345B 0.0s=> [internal] load .dockerignore 0.0s=> => transferring context: 2B 0.0s=> resolve image config for docker.io/docker/dockerfile:1 1.7s=> CACHED docker-image://docker.io/docker/dockerfile:1@sha256:ac85f380a63b13dfcefa89046420e1781752bab202122f8f50032edf31be0021 0.0s=> [internal] load metadata for docker.io/library/golang:1.20-alpine 1.2s=> [1/7] FROM docker.io/library/golang:1.20-alpine@sha256:3ae92bcab3301033767e8c13d401394e53ad2732f770c313a34630193ed009b8 0.0s=> [internal] load build context 0.0s=> => transferring context: 6.46kB 0.0s=> CACHED [2/7] WORKDIR /src 0.0s=> CACHED [3/7] COPY . . 0.0s=> CACHED [4/7] RUN go env -w GOPROXY=https://goproxy.cn 0.0s=> CACHED [5/7] RUN go mod download 0.0s=> CACHED [6/7] RUN go build -o /bin/client ./cmd/client 0.0s=> CACHED [7/7] RUN go build -o /bin/server ./cmd/server 0.0s=> exporting to image 0.0s=> => exporting layers 0.0s=> => writing image sha256:773f384bf110eaaf76123cec3e6072cef7868780da02929875e37909eee60c83 0.0s=> => naming to docker.io/library/buildme
可以看到,从第1步到第7步,都是 CACHED
。
查看image:
➜ buildme git:(main) ✗ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme latest 773f384bf110 45 minutes ago 416MB
......
从 CREATED
的值可见,实际上并没有重新构建image,因为每一步都没有发生变化。
接下来,我们修改源文件,比如对 cmd/client/main.go
文件添加一个注释,然后再次构建:
➜ buildme git:(main) ✗ docker build --tag=buildme .
[+] Building 26.8s (14/14) FINISHED docker:default=> [internal] load .dockerignore 0.0s=> => transferring context: 2B 0.0s=> [internal] load build definition from Dockerfile 0.0s=> => transferring dockerfile: 345B 0.0s=> resolve image config for docker.io/docker/dockerfile:1 1.4s=> CACHED docker-image://docker.io/docker/dockerfile:1@sha256:ac85f380a63b13dfcefa89046420e1781752bab202122f8f50032edf31be0021 0.0s=> [internal] load metadata for docker.io/library/golang:1.20-alpine 1.0s=> [1/7] FROM docker.io/library/golang:1.20-alpine@sha256:3ae92bcab3301033767e8c13d401394e53ad2732f770c313a34630193ed009b8 0.0s=> [internal] load build context 0.0s=> => transferring context: 6.69kB 0.0s=> CACHED [2/7] WORKDIR /src 0.0s=> [3/7] COPY . . 0.0s=> [4/7] RUN go env -w GOPROXY=https://goproxy.cn 0.2s=> [5/7] RUN go mod download 3.2s=> [6/7] RUN go build -o /bin/client ./cmd/client 19.1s=> [7/7] RUN go build -o /bin/server ./cmd/server 1.2s=> exporting to image 0.6s=> => exporting layers 0.6s=> => writing image sha256:05c59ce84ab98012b090ee3a6a67f6e1f2e9e998f81c56b18fdca04fe1dc6d6a 0.0s=> => naming to docker.io/library/buildme
可见,第3步没有从cache获取,因为 COPY
指令的源发生变化了。注意,随后的所有步骤,也都重新构建了。
查看image:
➜ buildme git:(main) ✗ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme latest 05c59ce84ab9 About a minute ago 416MB
<none> <none> 773f384bf110 47 minutes ago 416MB
......
显然,如果指令之间没有依赖关系,那么应该尽量把不变的步骤放在前面。
本例中, go mod download
是不变的,且非常耗时,但问题是, go mod download
下载的package,是在源代码里指定的,所以不能把它放在 COPY
指令前面。
解决办法:Go有两个记录项目依赖的文件,叫做 go.mod
和 go.sum
(注:这两个文件的作用,类似于JavaScript里的 package.json
和 package-lock.json
)。我们可以利用这两个文件,来判断 go mod download
是否需要重新构建。
修改 Dockerfile
文件如下:
# syntax=docker/dockerfile:1
FROM golang:1.20-alpine
WORKDIR /src
COPY go.mod go.sum .
RUN go env -w GOPROXY=https://goproxy.cn
RUN go mod download
COPY . .
RUN go build -o /bin/client ./cmd/client
RUN go build -o /bin/server ./cmd/server
ENTRYPOINT [ "/bin/server" ]
添加了 COPY go.mod go.sum .
。如果只是修改了源代码, go.mod
和 go.sum
没有变化,则 go mod download
无需重新构建。
构建一下,修改源代码,然后再次构建,就可以看到效果:
➜ buildme git:(main) ✗ docker build --tag=buildme .
[+] Building 14.6s (15/15) FINISHED docker:default=> [internal] load .dockerignore 0.0s=> => transferring context: 2B 0.0s=> [internal] load build definition from Dockerfile 0.0s=> => transferring dockerfile: 366B 0.0s=> resolve image config for docker.io/docker/dockerfile:1 0.7s=> CACHED docker-image://docker.io/docker/dockerfile:1@sha256:ac85f380a63b13dfcefa89046420e1781752bab202122f8f50032edf31be0021 0.0s=> [internal] load metadata for docker.io/library/golang:1.20-alpine 0.7s=> [1/8] FROM docker.io/library/golang:1.20-alpine@sha256:3ae92bcab3301033767e8c13d401394e53ad2732f770c313a34630193ed009b8 0.0s=> [internal] load build context 0.0s=> => transferring context: 6.70kB 0.0s=> CACHED [2/8] WORKDIR /src 0.0s=> CACHED [3/8] COPY go.mod go.sum . 0.0s=> CACHED [4/8] RUN go env -w GOPROXY=https://goproxy.cn 0.0s=> CACHED [5/8] RUN go mod download 0.0s=> [6/8] COPY . . 0.0s=> [7/8] RUN go build -o /bin/client ./cmd/client 11.1s=> [8/8] RUN go build -o /bin/server ./cmd/server 1.3s=> exporting to image 0.5s=> => exporting layers 0.5s=> => writing image sha256:fb86a9ea452afaec4f0c4f58248feef5a4447348fa90df089f5fc28abc8b4309 0.0s=> => naming to docker.io/library/buildme
可见,从第1步到第5步都是从cache获取。
例3:Multi-stage
优点:
- 并行构建,更快更高效
- 最小化image,只包含必需的东西
先前的例子里只用了一个stage,image大小为416MB,但实际上里面有很多东西是不需要的。
修改 Dockerfile
文件,添加一个 scratch
stage(注:“from scratch”是“从零开始”的意思),如下:
# syntax=docker/dockerfile:1
FROM golang:1.20-alpine
WORKDIR /src
COPY go.mod go.sum .
RUN go env -w GOPROXY=https://goproxy.cn
RUN go mod download
COPY . .
RUN go build -o /bin/client ./cmd/client
RUN go build -o /bin/server ./cmd/serverFROM scratch
COPY --from=0 /bin/client /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
在最终的image里,只保留 client
和 server
两个文件。
重新构建,然后查看image:
➜ buildme git:(main) ✗ docker images buildme
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme latest c8582385a23d 23 seconds ago 15.9MB
可见,image从原先的416MB减小到了15.9MB。
测试image,确保其工作正常。
接下来继续优化。本例中,client和server是串行构建的。由于构建client和构建server相互独立,为了提高效率,可以改为并行构建。
修改 Dockerfile
文件如下:
# syntax=docker/dockerfile:1
FROM golang:1.20-alpine AS base
WORKDIR /src
COPY go.mod go.sum .
RUN go env -w GOPROXY=https://goproxy.cn
RUN go mod download
COPY . .FROM base AS build-client
RUN go build -o /bin/client ./cmd/clientFROM base AS build-server
RUN go build -o /bin/server ./cmd/serverFROM scratch
COPY --from=build-client /bin/client /bin/
COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
重新构建,观察构建过程,可见client和server是一起构建的。
测试image,确保其工作正常。
经过上述优化,image小了很多,client和server是并行构建的。接下来,还可以进一步优化,把client和server分成两个不同的image。
同一个Dockerfile可以构建不同的image,方法是在构建时,通过 --target
选项指定目标stage。
修改 Dockerfile
文件如下:
# syntax=docker/dockerfile:1
FROM golang:1.20-alpine AS base
WORKDIR /src
COPY go.mod go.sum .
RUN go env -w GOPROXY=https://goproxy.cn
RUN go mod download
COPY . .FROM base AS build-client
RUN go build -o /bin/client ./cmd/clientFROM base AS build-server
RUN go build -o /bin/server ./cmd/serverFROM scratch AS client
COPY --from=build-client /bin/client /bin/
ENTRYPOINT [ "/bin/client" ]FROM scratch AS server
COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
然后用不同的命令构建client和server:
- client:
docker build --tag=buildme-client --target=client .
- server:
docker build --tag=buildme-server --target=server .
查看image:
➜ buildme git:(main) ✗ docker images "buildme*"
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme-server latest f26347f19b1e 3 seconds ago 7.91MB
buildme-client latest f92304f7b995 9 minutes ago 7.98MB
buildme latest 36bf26ddaf59 9 minutes ago 15.9MB
可见,把client和server分开后,各自的image更小了。
注意:如果指定了目标stage,则Docker只运行相关的stage。本例中,如果指定构建client,则 build-server
和 server
stage会被略过。同理,如果指定构建server,则 build-client
和 client
stage会被略过。
注:把client和server分开后,server能够运行,但是client无法连接到server,因为指定的是 http://localhost
。需要做一些额外处理才行,已超出本文范围,不做赘述。
例4:Mount
本例将涉及以下两种mount:
- cache mount
- bind mount
cache mount
顾名思义,就是把文件做缓存。我感觉它像是一个全局的目录,大家都可以访问它,向其做读写操作。
修改 Dockerfile
文件如下:
# syntax=docker/dockerfile:1
FROM golang:1.20-alpine AS base
WORKDIR /src
COPY go.mod go.sum .
RUN go env -w GOPROXY=https://goproxy.cn
RUN --mount=type=cache,target=/go/pkg/mod/ \go mod download -x
COPY . .FROM base AS build-client
RUN --mount=type=cache,target=/go/pkg/mod/ \go build -o /bin/client ./cmd/clientFROM base AS build-server
RUN --mount=type=cache,target=/go/pkg/mod/ \go build -o /bin/server ./cmd/serverFROM scratch AS client
COPY --from=build-client /bin/client /bin/
ENTRYPOINT [ "/bin/client" ]FROM scratch AS server
COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
注: go mod download
命令的 -x
选项会打印出下载的情况。感觉有点类似bash的 -x
选项。
在重新构建之前,先清掉cache:
docker builder prune -af
其中:
-a
:表示all-f
:表示force
注:可以用 docker builder prune --help
命令查看帮助。
重新构建:
docker build --target=client --progress=plain . 2> log1.txt
注意:添加了 --progress=plain
选项,同时把输出(貌似 2
代表错误输出stderr)重定向到 log1.txt
文件。
查看 log1.txt
文件:
......
#12 0.299 # get https://goproxy.cn/github.com/charmbracelet/bubbletea/@v/v0.23.1.mod
#12 0.299 # get https://goproxy.cn/github.com/aymanbagabas/go-osc52/@v/v1.0.3.mod
#12 0.299 # get https://goproxy.cn/github.com/charmbracelet/bubbles/@v/v0.14.0.mod
#12 0.300 # get https://goproxy.cn/github.com/atotto/clipboard/@v/v0.1.4.mod
#12 0.361 # get https://goproxy.cn/github.com/atotto/clipboard/@v/v0.1.4.mod: 200 OK (0.061s)
#12 0.361 # get https://goproxy.cn/github.com/charmbracelet/bubbletea/@v/v0.23.1.mod: 200 OK (0.062s)
#12 0.361 # get https://goproxy.cn/github.com/charmbracelet/bubbles/@v/v0.14.0.mod: 200 OK (0.062s)
#12 0.362 # get https://goproxy.cn/github.com/aymanbagabas/go-osc52/@v/v1.0.3.mod: 200 OK (0.062s)
#12 0.363 # get https://goproxy.cn/github.com/charmbracelet/lipgloss/@v/v0.6.0.mod
#12 0.363 # get https://goproxy.cn/github.com/go-chi/chi/v5/@v/v5.0.0.mod
#12 0.363 # get https://goproxy.cn/github.com/containerd/console/@v/v1.0.3.mod
#12 0.364 # get https://goproxy.cn/github.com/lucasb-eyer/go-colorful/@v/v1.2.0.mod
#12 0.379 # get https://goproxy.cn/github.com/go-chi/chi/v5/@v/v5.0.0.mod: 200 OK (0.016s)
......
把package chi
的版本改为 v5.0.8
:
docker run -v $PWD:$PWD -w $PWD golang:1.20-alpine \go get github.com/go-chi/chi/v5@v5.0.8
注意:原文档上是 golang:1.21-alpine
,但是git里都是 golang:1.20-alpine
,所以我用了后者。
由于网络连接问题,运行报错如下:
go: github.com/atotto/clipboard@v0.1.4: Get "https://proxy.golang.org/github.com/atotto/clipboard/@v/v0.1.4.mod": dial tcp 172.217.163.49:443: i/o timeout
解决办法还是添加代理。
查看 docker run
的帮助,如下:
➜ buildme git:(main) ✗ docker help runUsage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
......
只能运行一条命令。要想运行多条命令,需要迂回一下:
docker run -v $PWD:$PWD -w $PWD golang:1.20-alpine \sh -c "go env -w GOPROXY=https://goproxy.cn; go get github.com/go-chi/chi/v5@v5.0.8"
查看 go.mod
:
......github.com/go-chi/chi/v5 v5.0.8
......
注:原先是 v5.0.0
。
查看image:
➜ buildme git:(main) ✗ docker images golang
REPOSITORY TAG IMAGE ID CREATED SIZE
golang 1.20-alpine f62d76c5566c 2 weeks ago 255MB
并没有变化。
现在,再构建一次:
docker build --target=client --progress=plain . 2> log2.txt
查看 log2.txt
文件:
......
#12 [base 6/7] RUN --mount=type=cache,target=/go/pkg/mod/ go mod download -x
#12 0.283 # get https://goproxy.cn/github.com/go-chi/chi/v5/@v/v5.0.8.mod
#12 0.353 # get https://goproxy.cn/github.com/go-chi/chi/v5/@v/v5.0.8.mod: 200 OK (0.071s)
#12 0.354 # get https://goproxy.cn/github.com/go-chi/chi/v5/@v/v5.0.8.info
#12 0.372 # get https://goproxy.cn/github.com/go-chi/chi/v5/@v/v5.0.8.info: 200 OK (0.018s)
#12 0.374 # get https://goproxy.cn/github.com/go-chi/chi/v5/@v/v5.0.8.zip
#12 0.393 # get https://goproxy.cn/github.com/go-chi/chi/v5/@v/v5.0.8.zip: 200 OK (0.019s)
#12 DONE 0.5s
......
可见,只下载了和 chi
相关的package。
注:这应该是与Go的module管理机制有关,它用到了 /go/pkg/mod/
目录,不然它怎么知道这次只需下载 chi
的 v5.0.8
版本呢。
bind mount
在构建期,把宿主机或者其它stage里的文件/目录mount过来。
本例中, go.mod
和 go.sum
,这两个文件是复制到容器里的。通过bind mount,可使容器直接访问宿主机上的文件,从而省略所对应的 COPY
指令。
修改 Dockerfile
文件如下:
# syntax=docker/dockerfile:1
FROM golang:1.20-alpine AS base
WORKDIR /src
RUN go env -w GOPROXY=https://goproxy.cn
RUN --mount=type=cache,target=/go/pkg/mod/ \--mount=type=bind,source=go.sum,target=go.sum \--mount=type=bind,source=go.mod,target=go.mod \go mod download -x
COPY . .FROM base AS build-client
RUN --mount=type=cache,target=/go/pkg/mod/ \go build -o /bin/client ./cmd/clientFROM base AS build-server
RUN --mount=type=cache,target=/go/pkg/mod/ \go build -o /bin/server ./cmd/serverFROM scratch AS client
COPY --from=build-client /bin/client /bin/
ENTRYPOINT [ "/bin/client" ]FROM scratch AS server
COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
本例中, source=go.sum
:这是宿主机上的文件,貌似只能用相对路径(以宿主机当前目录为基础),不能用绝对路径,否则会报错找不到。
另外,source也可以指定为其它stage,要加上 from=<stage>
选项。
mount的文件/目录只在构建期的当前指令范围内可见。
本例中挂载的是文件,若挂载的是目录,则该目录是只读的。
注:docker run
命令也可以做bind mount,当mount宿主机的目录时,该目录并不是只读的。
同理,也可以把下面的 COPY
指令做相同处理。
修改 Dockerfile
文件如下:
# syntax=docker/dockerfile:1
FROM golang:1.20-alpine AS base
WORKDIR /src
RUN go env -w GOPROXY=https://goproxy.cn
RUN --mount=type=cache,target=/go/pkg/mod/ \--mount=type=bind,source=go.sum,target=go.sum \--mount=type=bind,source=go.mod,target=go.mod \go mod download -xFROM base AS build-client
RUN --mount=type=cache,target=/go/pkg/mod/ \--mount=type=bind,target=. \go build -o /bin/client ./cmd/clientFROM base AS build-server
RUN --mount=type=cache,target=/go/pkg/mod/ \--mount=type=bind,target=. \go build -o /bin/server ./cmd/serverFROM scratch AS client
COPY --from=build-client /bin/client /bin/
ENTRYPOINT [ "/bin/client" ]FROM scratch AS server
COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
注意:本例中没有指定source,我在官网文档没有查到其默认值是什么,不过通过试验可知,source的默认值应该是 .
(宿主机的当前目录)。
例5:参数
本例中,parent image指定为 golang:1.20-alpine
。为了随时想切换到别的版本,我们可以在Dockerfile里使用变量,而在构建时传入所需的版本号。
修改 Dockerfile
文件如下:
# syntax=docker/dockerfile:1
ARG GO_VERSION=1.20
FROM golang:${GO_VERSION}-alpine AS base
WORKDIR /src
RUN go env -w GOPROXY=https://goproxy.cn
RUN --mount=type=cache,target=/go/pkg/mod/ \--mount=type=bind,source=go.sum,target=go.sum \--mount=type=bind,source=go.mod,target=go.mod \go mod download -xFROM base AS build-client
RUN --mount=type=cache,target=/go/pkg/mod/ \--mount=type=bind,target=. \go build -o /bin/client ./cmd/clientFROM base AS build-server
RUN --mount=type=cache,target=/go/pkg/mod/ \--mount=type=bind,target=. \go build -o /bin/server ./cmd/serverFROM scratch AS client
COPY --from=build-client /bin/client /bin/
ENTRYPOINT [ "/bin/client" ]FROM scratch AS server
COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
可以在构建时通过 --build-arg
选项传入参数:
docker build --target=client --build-arg="GO_VERSION=1.19" .
如果不传入参数,则使用其缺省值 1.20
。
同理,也可以在构建时把参数传到源代码里。
本例中, cmd/server/main.go
文件内容如下:
......
var version stringfunc main() {if version != "" {log.Printf("Version: %s", version)}
......
在Go语言中,通过 -ldflags
选项传入参数。例如:
go build -ldflags "-X main.version=v0.0.1" -o /bin/server ./cmd/server
因此,在Dockerfile里,可以设置变量 APP_VERSION
,在构建时传入参数。
修改 Dockerfile
文件如下:
# syntax=docker/dockerfile:1
ARG GO_VERSION=1.20
FROM golang:${GO_VERSION}-alpine AS base
WORKDIR /src
RUN go env -w GOPROXY=https://goproxy.cn
RUN --mount=type=cache,target=/go/pkg/mod/ \--mount=type=bind,source=go.sum,target=go.sum \--mount=type=bind,source=go.mod,target=go.mod \go mod download -xFROM base AS build-client
RUN --mount=type=cache,target=/go/pkg/mod/ \--mount=type=bind,target=. \go build -o /bin/client ./cmd/clientFROM base AS build-server
ARG APP_VERSION="v0.0.0+unknown"
RUN --mount=type=cache,target=/go/pkg/mod/ \--mount=type=bind,target=. \go build -ldflags "-X main.version=$APP_VERSION" -o /bin/server ./cmd/serverFROM scratch AS client
COPY --from=build-client /bin/client /bin/
ENTRYPOINT [ "/bin/client" ]FROM scratch AS server
COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
构建server:
docker build --target=server --build-arg="APP_VERSION=v0.0.1" --tag=buildme-server .
运行server:
➜ buildme git:(main) ✗ docker run buildme-server
2023/12/29 14:39:02 Version: v0.0.1
2023/12/29 14:39:02 Starting server...
2023/12/29 14:39:02 Listening on HTTP port 3000
例6:Export文件
docker build
默认的输出是容器image。image被载入image store,你可以为该image启动一个容器,或者把它push到registry。这种行为使用的是缺省的exporter,称为 docker
exporter。
你也可以使用 local
exporter,其构建结果为文件。在构建时,传入 --output
选项,指定目标路径。例如:
docker build --output=. --target=server .
本例中,指定目标路径为当前目录。注意实际export的路径为宿主机的 ./bin/server
,这是因为Dockerfile指定的目标路径是 /bin/server
。
查看文件:
➜ buildme git:(main) ✗ ll bin
total 7.6M
-rwxr-xr-x. 1 ding ding 7.6M Dec 29 22:52 server
如果想要把client和server一起export,可以在 Dockerfile
文件里添加一个stage,如下:
......
FROM scratch AS binaries
COPY --from=build-client /bin/client /
COPY --from=build-server /bin/server /
重新构建:
docker build --output=bin --target=binaries .
注:为了使export的文件仍然在 ./bin
目录下,因为Dockerfile里的目标路径是 /
,所以构建时指定的output路径是 bin
。
查看文件:
➜ buildme git:(main) ✗ ll bin
total 16M
-rwxr-xr-x. 1 ding ding 7.7M Dec 29 23:02 client
-rwxr-xr-x. 1 ding ding 7.6M Dec 29 23:02 server
例7:测试
本例中,源代码是Go语言,所以,接下来我们使用 golangci-lint
来做检查,比如代码中是否有错误、语法和注释是否规范等。
golangci-lint
有现成的image,我们先来试用一下:
docker run -v $PWD:/test -w /test \golangci/golangci-lint golangci-lint run
报错如下:
level=error msg="Running error: context loading failed: failed to load packages: timed out to load packages: context deadline exceeded"
level=error msg="Timeout exceeded: try increasing it by passing --timeout option"
解决办法:按提示,增加超时时间:
docker run -v $PWD:/test -w /test \golangci/golangci-lint golangci-lint run --timeout=10m
运行结果如下:
cmd/client/ui.go:5:2: "strings" imported and not used (typecheck)"strings"^
cmd/server/main.go:18:7: undefined: chi (typecheck)r := chi.NewRouter()^
注:和官网文档中的不太一样。我在其它几个机器(操作系统分别是 Ubuntu 22.04
和 RHEL 9.2
)上测试,和官网文档所说的报错是一致的:
cmd/server/main.go:23:10: Error return value of `w.Write` is not checked (errcheck)w.Write([]byte(translated))^
我仔细对比了一下环境,也没看出有何不同,有待继续研究。
注:这可能是个false alarm。如果想要修复,可以把代码修改如下:
......// w.Write([]byte(translated))if _, err := w.Write([]byte(translated)); err != nil {log.Println(err)
......
不过为了下面的例子演示,还是先别修复了。
接下来,我们将其加入到Dockerfile里。
修改 Dockerfile
文件如下:
# syntax=docker/dockerfile:1
ARG GO_VERSION=1.20
ARG GOLANGCI_LINT_VERSION=v1.52
FROM golang:${GO_VERSION}-alpine AS base
WORKDIR /src
RUN go env -w GOPROXY=https://goproxy.cn
RUN --mount=type=cache,target=/go/pkg/mod/ \--mount=type=bind,source=go.sum,target=go.sum \--mount=type=bind,source=go.mod,target=go.mod \go mod download -xFROM base AS build-client
RUN --mount=type=cache,target=/go/pkg/mod/ \--mount=type=bind,target=. \go build -o /bin/client ./cmd/clientFROM base AS build-server
ARG APP_VERSION="v0.0.0+unknown"
RUN --mount=type=cache,target=/go/pkg/mod/ \--mount=type=bind,target=. \go build -ldflags "-X main.version=$APP_VERSION" -o /bin/server ./cmd/serverFROM scratch AS client
COPY --from=build-client /bin/client /bin/
ENTRYPOINT [ "/bin/client" ]FROM scratch AS server
COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]FROM scratch AS binaries
COPY --from=build-client /bin/client /
COPY --from=build-server /bin/server /FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION} as lint
WORKDIR /test
RUN --mount=type=bind,target=. \golangci-lint run --timeout=10m
注意:别忘了加 --timeout=10m
。
构建:
docker build --target=lint .
运行结果如下:
➜ buildme git:(main) ✗ docker build --target=lint .
[+] Building 84.5s (9/9) FINISHED docker:default=> [internal] load .dockerignore 0.0s=> => transferring context: 2B 0.0s=> [internal] load build definition from Dockerfile 0.0s=> => transferring dockerfile: 1.25kB 0.0s=> resolve image config for docker.io/docker/dockerfile:1 1.5s=> CACHED docker-image://docker.io/docker/dockerfile:1@sha256:ac85f380a63b13dfcefa89046420e1781752bab202122f8f50032edf31be0021 0.0s=> [internal] load metadata for docker.io/golangci/golangci-lint:v1.52 1.0s=> [lint 1/3] FROM docker.io/golangci/golangci-lint:v1.52@sha256:3d2f4240905054c7efa7f4e98ba145c12a16995bbc3e605300e21400a1665cb6 0.0s=> [internal] load build context 0.0s=> => transferring context: 7.00kB 0.0s=> CACHED [lint 2/3] WORKDIR /test 0.0s=> ERROR [lint 3/3] RUN --mount=type=bind,target=. golangci-lint run --timeout=10m 81.9s
------ > [lint 3/3] RUN --mount=type=bind,target=. golangci-lint run --timeout=10m:
81.86 cmd/client/ui.go:5:2: "strings" imported and not used (typecheck)
81.86 "strings"
81.86 ^
81.86 cmd/server/main.go:18:7: undefined: chi (typecheck)
81.86 r := chi.NewRouter()
81.86 ^
------
Dockerfile:37
--------------------36 | WORKDIR /test37 | >>> RUN --mount=type=bind,target=. \38 | >>> golangci-lint run --timeout=10m39 |
--------------------
ERROR: failed to solve: process "/bin/sh -c golangci-lint run --timeout=10m" did not complete successfully: exit code: 1
注意:由于 golangci-lint
检测出问题(exit code为1),实际上构建失败了。
➜ buildme git:(main) ✗ echo $?
1
注:官网文档上说必须加上 --target=lint
。
接下来,我们把检测结果export到文件。官网文档提供了大致思路,其实和前面例6的过程是一样的,步骤如下:
- 把检测结果输出到文件。
- 创建一个新的stage,使用
scratch
作为base image,复制结果文件。 - 构建时指定
--output
选项。
官网文档没有提供具体Dockerfile,而是留给读者作为练习。
要想把检测结果输出到文件,可以通过输出重定向的方式,我测试发现,检测的结果是通过stderr输出的。
注:如果想要详细的输出,可以给 golangci-lint
加上 -v
选项。
另外,有一点需要注意:由于 golangci-lint
检测出问题,实际上构建失败了,随后的指令也不会再运行,所以必须要忽略错误,才能继续构建。
Docker是通过命令的返回值(exit code)来判断是否成功,因此,可以强制让命令返回0。
修改 Dockerfile
文件如下:
# syntax=docker/dockerfile:1
ARG GO_VERSION=1.20
ARG GOLANGCI_LINT_VERSION=v1.52
FROM golang:${GO_VERSION}-alpine AS base
WORKDIR /src
RUN go env -w GOPROXY=https://goproxy.cn
RUN --mount=type=cache,target=/go/pkg/mod/ \--mount=type=bind,source=go.sum,target=go.sum \--mount=type=bind,source=go.mod,target=go.mod \go mod download -xFROM base AS build-client
RUN --mount=type=cache,target=/go/pkg/mod/ \--mount=type=bind,target=. \go build -o /bin/client ./cmd/clientFROM base AS build-server
ARG APP_VERSION="v0.0.0+unknown"
RUN --mount=type=cache,target=/go/pkg/mod/ \--mount=type=bind,target=. \go build -ldflags "-X main.version=$APP_VERSION" -o /bin/server ./cmd/serverFROM scratch AS client
COPY --from=build-client /bin/client /bin/
ENTRYPOINT [ "/bin/client" ]FROM scratch AS server
COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]FROM scratch AS binaries
COPY --from=build-client /bin/client /
COPY --from=build-server /bin/server /FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION} as lint
WORKDIR /test
RUN --mount=type=bind,target=. \golangci-lint run --timeout=10m > /1.out 2>&1 || trueFROM scratch AS testresult
COPY --from=lint /1.out /
注意:重定向到根目录( /1.out
),不能重定向到当前目录( 1.out
),因为在容器里,当前目录是从宿主机映射而来,是只读的(没有指定source,默认值是 .
,即宿主机的当前目录)。
构建:
docker build --target=testresult --output=. .
构建成功了(虽然检测出了问题)。
查看 1.out
文件:
cmd/client/ui.go:5:2: "strings" imported and not used (typecheck)"strings"^
cmd/server/main.go:18:7: undefined: chi (typecheck)r := chi.NewRouter()
参考
https://docs.docker.com/build/guide/
https://goproxy.cn
https://www.cnblogs.com/wt645631686/p/13405161.html
https://blog.51cto.com/u_16213347/7230157
https://stackoverflow.com/questions/76287900/perform-docker-official-guide-still-getting-error-of-stage-build-with-docker-7
https://taskfile.dev
相关文章:
使用docker build构建image
文章目录 环境步骤准备例1:基本用法例2:缓存layer例3:Multi-stage例4:Mountcache mountbind mount 例5:参数例6:Export文件例7:测试 参考 环境 RHEL 9.3Docker Community 24.0.7 步骤 在Dock…...
【亲测有效】Win11 卸载MySQL5.7以及安装MySQL8.0.35
目录 一、卸载原来本地的mysql5.7 1.mysql服务部分 1.1停止mysql服务 1.2删除mysql服务 2.卸载 MySQL程序 3.残余文件的清理 3.1删除mysql安装的目录 3.2删除mysql数据存放的目录 3.3删除mysql自定义目录 4.清理注册表 5.删除环境变量配置 二、安装mysql8.0.35 1.…...
Beauty algorithm(三)腮红
查阅资料了解到腮红位于苹果肌处,同样使用关键点确定目标区域,然后对该区域进行渲染达到美妆效果。考虑到如果使用简单的RGB是很难做到特效,本篇采用模板方式进行区域融合。 一、skills 前瞻 1、png图像读取 cv::imread(imgPath, cv::IMREAD_UNCHANGED) IMREAD_UNCHANGE…...
DNS安全与访问控制
一、DNS安全 1、DNSSEC原理 DNSSEC依靠数字签名保证DNS应答报文的真实性和完整性。权威域名服务器用自己的私有密钥对资源记录(Resource Record, RR)进行签名,解析服务器用权威服务器的公开密钥对收到的应答信息进行验证。如果验证失败&…...
【LMM 011】MiniGPT-5:通过 Generative Vokens 进行交错视觉语言生成的多模态大模型
论文标题:MiniGPT-5: Interleaved Vision-and-Language Generation via Generative Vokens 论文作者:Kaizhi Zheng* , Xuehai He* , Xin Eric Wang 作者单位:University of California, Santa Cruz 论文原文:https://arxiv.org/ab…...
WEB 3D技术 three.js 顶点交换
本文 我们来说 顶点的转换 其实就是 我们所有顶点的位置发生转变 我们整个物体的位置也会随之转变 这里 我们编写代码如下 import ./style.css import * as THREE from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.j…...
ROS学习笔记(11)进一步深入了解ROS第五步
0.前提 我在学习宾夕的ROS公开课的时候发现,外国的对计算机的教育和国内的是完全不一样的,当你接触了外国的课程后回头看自己学的会发现好像自己啥也没学。我这里可以放出来给大家看一下。 1.Python and C 2.Python PDB Tutorial:Python Deb…...
性能优化-OpenMP基础教程(四)-Android上运行OpenMP
本文主要介绍如何在一个常规的Android手机上调试OpenMP程序,包括Android NDK的环境配置和使用JNI编写一个OpenMP程序运行在Android手机中。 🎬个人简介:一个全栈工程师的升级之路! 📋个人专栏:高性能&#…...
【转载】-财报-丈母娘教咱看财报(资产负债表-利润表-现金流量表)
写在前面 近期,在知乎看到“云峰金融”的一篇关于金融知识的文章《丈母娘教你看财报》,挺有意思的,挑出核心内容,又添加了一些内容的解释,特来分享一下。对于金融入门小白来讲,非常友好。如有不正确的地方&…...
HTML5大作业-精致版个人博客空间模板源码
文章目录 1.设计来源1.1 博客主页界面1.2 博主信息界面1.3 我的文章界面1.4 我的相册界面1.5 我的工具界面1.6 我的源码界面1.7 我的日记界面1.8 我的留言板界面1.9 联系博主界面 2.演示效果和结构及源码2.1 效果演示2.2 目录结构2.3 源代码 源码下载 作者:xcLeigh …...
数字IC后端设计实现之Innovus update_names和changeInstName的各种应用场景
今天吾爱IC社区小编给大家分享下数字IC后端设计实现innovus中关于update_names和changeInstName在PR中的具体使用方法。 update_names 1)为了避免和verilog语法保留的一些关键词,比如input,output这些,是不允许存在叫这类名字的…...
1月6日,每日信息差
1、世界最大冰雪主题乐园!哈尔滨冰雪大世界获吉尼斯世界纪录,吉尼斯世界纪录大中华地区首位认证官吴晓红宣布,哈尔滨冰雪大世界面积为816682.5平方米,是世界上最大的冰雪主题乐园,荣获一项新的吉尼斯世界纪录称号 2、…...
部署上传漏洞的靶场环境upload-labs
1、工具介绍 upload-labs是一个使用php语言编写的,专门收集渗透测试和CTF中遇到的各种上传漏洞的靶场。旨在帮助大家对上传漏洞有一个全面的了解。目前一共20关,每一关都包含着不同上传方式。 upload-labs靶场开源地址::https://…...
Linux的压缩与解压
一、tar命令 语法:tar [-c -v -x -f -z -C] 参数1 参数2 参数3 ....-c:创建压缩文件,用于压缩模式-v:显示压缩、解压过程,用于查看进度-x:解压模式-f:要创建的文件,或者要解压的文件…...
互联网大厂面试题目
阿里篇 1.1.1 如何实现一个高效的单向链表逆序输出? 1.1.2 已知sqrt(2)约等于1.414,要求不用数学库,求sqrt(2)精确到小数点后10位 1.1.3 给定一个二叉搜索树(BST),找到树中第 K 小的节点 1.1.4 LRU缓存机制 1.1.5 关于epoll和…...
单文件上传
随着Web应用的普及,文件上传功能成为许多网站和应用不可或缺的一部分。本文整理了个人学习过程中的笔记,为开发者提供全面的了解和实践经验。 单文件上传 在早期的html应用中,都是使用form标签中嵌套来实现文件上传的,具体代码如…...
美经济学家预测,明年美股或将大跌86%,你怎么看?
年初至今,标准普尔500指数上涨25%,道琼斯指数上涨13%,以科技股为主的纳斯达克指数大涨了44%。 美国经济学家哈里斯登特近日预测,这种牛市是“100%人为印钞的结果”,而这一巨大的泡沫将在2024年破灭,届时美…...
【BIAI】lecture 3 - GD BP CNN Hands-on
GD & BP & CNN & Hands-on 专业术语 gradient descent (GD) 梯度下降 back propagation (BP) 向传播 Convolutional Neural Network (CNN) 卷积神经网络 forward propagation 前向传播 biologically symmetry 生物对称性 synaptic 突触 axon 轴突 课程大纲 The go…...
计算机Java项目|基于SpringBoot+Vue的图书个性化推荐系统
项目编号:L-BS-GX-10 一,环境介绍 语言环境:Java: jdk1.8 数据库:Mysql: mysql5.7 应用服务器:Tomcat: tomcat8.5.31 开发工具:IDEA或eclipse 二,项目简介 图片管理系统是一个为学生和…...
lenovo联想小新Pro-13 2020 Intel IML版笔记本电脑(82DN)原装出厂Win10系统镜像
链接:https://pan.baidu.com/s/1bJpfXudYEC7MJ7qfjDYPdg?pwdjipj 提取码:jipj 原装出厂Windows10系统自带所有驱动、出厂主题壁纸、系统属性专属LOGO标志、Office办公软件、联想电脑管家等预装程序 所需要工具:16G或以上的U盘 文件格式&a…...
54、Softmax 分类器以及它的底层原理
下面开始介绍最后一个算法softmax。在前面介绍全连接算法或其他文章中,或多或少也提到了softmax。 在分类网络里,softmax的作用主要是将模型的原始输出映射到 0~1之间的概率分布。很多时候对于我们初学者而言,只知道softmax可以做概率映射,但并不了解它内部的原理是如何完…...
【React】class组件生命周期函数的梳理和总结(第一篇)
1. 前言 本篇梳理和总结一下React的生命周期函数,方便使用class组件的同学查阅,先上生命周期图谱。 2. 生命周期函数 生命周期函数说明constructor(props) 功能:如果不需要初始化state或不进行方法绑定,class组件可以不用实现构造…...
[每周一更]-(第49期):一名成熟Go开发需储备的知识点(答案篇)- 2
答案篇 1、Go语言基础知识 什么是Go语言?它有哪些特点? Go语言(也称为Golang)是一种由Google开发的开源编程语言。它于2007年首次公开发布,并在2012年正式推出了稳定版本。Go语言旨在提供简单、高效、可靠的编程解决…...
23种设计模式Python版
目录 创建型模式简单工厂模式工厂方法模式抽象工厂模式单例模式原型模式建造者模式 结构型模式适配器模式桥接模式组合模式装饰器模式外观模式享元模式代理模式 行为型模式职责链模式命令模式解释器模式迭代器模式中介者模式备忘录模式观察者模式状态模式策略模式模板方法模式访…...
2024年汉字小达人区级选拔备考——真题做一做:诗词连线
前面,六分成长介绍了汉字小达人区级选拔样题的第一大题看拼音写汉字,以及第二大题补充成语。这两道题都是填空题,通常在学校进行线下选拔的时候使用。这篇文章介绍第三大题:诗词连线。 诗词连线是2022年(第九届&#x…...
Vite scss 如何引入本地 字体
Vite scss 如何引入本地 字体 最近在用 Vite 改造一个旧项目 Diary,遇到了好多从 Vue 转到 Vite 的问题。 这次这个问题是: scss 里本地字体引入的问题。 一、问题描述 可以看到下面的卡片字体,本来应该是 impact 的,但现在无法…...
扩展 apiserver 连接认证 ip, apiserver证书更新
本文来自我的博客地址 文章目录 问题场景:问题分析:问题解决:查看 apiserver 证书支持的 ip 或 host使用 openssl 生成证书:再次查看 apiserver 证书支持的 ip 或 host 再次尝试将 master 加点加入参考 问题场景: k8s 1.28.1 集群后期新增 vip apiserver 证书不支持 vip 引入…...
VUE--保留小数(过滤器)
1.cutOutNum.js export const cutOutNum (num, decimals) > {if (isNaN(num) || (!num && num ! 0)) {return "-";}function toNonExponential(_num) {var m Number(_num).toExponential().match(/\d(?:\.(\d*))?e([-]\d)/);return Number(_num).toF…...
书生·浦语大模型实战营第一次课堂笔记
书生浦语大模型全链路开源体系。大模型是发展通用人工智能的重要途径,是人工通用人工智能的一个重要途径。书生浦语大模型覆盖轻量级、重量级、重量级的三种不同大小模型,可用于智能客服、个人助手等领域。还介绍了书生浦语大模型的性能在多个数据集上全面超过了相似量级或相近…...
Mysql为什么只能支持2000w左右的数据量?
首先说明一下: MySQL并没有硬性规定只能支持到2000万左右的数据量。 其实,MySQL能够处理的数据量远远超过这个数字。无论是开源社区版还是商业版, MySQL在适当的硬件和配置下,都能够支持非常大的数据集。 通常所说的“MySQL只能…...
免费软件下载网站排行/济南做网站比较好的公司
加上引号就好了👏 def foo(value: str|int|float|list|tuple):print(value)foo(None)...
做网站看网页效果/安徽百度推广怎么做
课程名称:2016级计算机科学与工程学院软件工程(西北师范大学) 课程要求:实验一 软件工程准备 三个问题: Q1: 在没有看《构建之法》这本书之前,我对软件工程这门课的概念很模糊,对什么是软件&…...
网站开发 私活/seo基础培训机构
因世界首例基因编辑婴儿引发巨大争议的贺建奎也名列其中,成为了今年榜单的“反面案例”。 12月19日零时,世界顶尖学术期刊、英国《自然》杂志(Nature)发布了2018年度影响世界的十大科学人物。其中,中国22岁天才少年曹…...
继续坚持网站建设监管/百度统计收费吗
MySQL有多种存储引擎,每种存储引擎有各自的优缺点,可以择优选择使用:MyISAM、InnoDB、MERGE、MEMORY(HEAP)、BDB(BerkeleyDB)、EXAMPLE、FEDERATED、ARCHIVE、CSV、BLACKHOLE。 mysql的存储引擎包括:MyISAM、InnoDB、BDB、MEMORY、…...
想自己做个网站/免费网站排名优化软件
金庸笔下的良好代码风格 转至:http://kb.cnblogs.com/page/202642/作者: 王路 发布时间: 2014-03-23 14:00 阅读: 8964 次 推荐: 43 原文链接 [收藏] 我零九年看过一本小说,讲程序员的故事,从此,了解了一个新物种。最近又…...
前端做任务的网站/中超最新积分榜
1.1 指定运行级别运行级别说明: 0 : 关机 1 : 单用户[召回丢失密码] 2 : 多用户状态没有网络服务 3 : 多用户状态有网络服务 4 : 系统未使用保留给用户 5 : 图形界面 6 : 系统重启常用运行级别是 3 和 5 ,要修改默认的运行级别可改文件 /etc/inittab 的 id:5:initde…...