EDA365电子论坛网

标题: docker compose 服务启动顺序控制 [打印本页]

作者: baqiao    时间: 2020-8-10 15:11
标题: docker compose 服务启动顺序控制
概要& ^1 f$ Y; {6 Q! x0 `
docker-compose 可以方便组合多个 docker 容器服务, 但是, 当容器服务之间存在依赖关系时, docker-compose 并不能保证服务的启动顺序.
% H' K6 w+ Z: p" n! `$ ~# f9 p1 X- W! }, ~3 U7 f/ w
docker-compose 中的 depends_on 配置是容器的启动顺序, 并不是容器中服务的启动顺序.7 f) I% B% r1 n; {6 W1 }& }
+ O; ~/ H1 N  U
问题重现
5 h7 y7 r5 ^/ W1 M0 O首先, 我们构造一个示例, 来演示 docker-compose 带来的问题. docker-compose.yml 文件如下:
$ C7 g! d7 h* r& H2 }- {' d4 X; D7 t7 X& e; h
version: '2'$ I/ j/ [9 q# r7 R# D- y
services:: r! d7 w  I5 \8 o  _+ ^5 ^
  web:
: |$ E% M7 X, j  W/ e6 Z* i    image: ubuntu:14.04
* t" p3 X/ ]+ ^$ ?5 `    depends_on:
# i& v, }8 H4 r6 F- p      - web
9 N3 u( i' B, G7 M' }    command: nc -z database 3306
( w# Z, _* F" S1 T5 D/ D9 D# W, ^1 j
  database:( g: f8 [5 b# n3 [9 ?
    image: ubuntu:14.04
7 e( K" b6 _% q0 x* t2 \    command: >
/ M1 j" ?6 B. @      /bin/bash -c '& H% Q% M3 E8 `# L" j2 s
      sleep 5;
; o$ U6 @6 H/ p      echo "sleep over";+ h+ I# E  X' g
      nc -lk 0.0.0.0 3306;* {  k7 ~( I" _) C) P* R" ]
      '/ w$ m* `: L* G  \& _8 d2 n' W
启动后, 可以发现, 确实是先启动 database, 后启动 web, 但是 database 中的服务是在大约 5 秒后才完成的, 所以导致 web 的启动失败.
7 n" u0 o# n) f$ w' h7 n* D
* E) i2 m% q3 Q& |& ~* ^7 p$ docker-compose up
2 ?8 l& {/ Z; ECreating tmp_database_1 ... done0 `* s( \! \# J$ N/ ]5 G8 w! b
Creating tmp_database_1 ...
, N! F, B0 P5 y# t7 {+ p2 G. l$ ~Creating tmp_web_1      ... done- W) k% w+ J; _2 d
Attaching to tmp_database_1, tmp_web_1
0 a) @9 K5 q0 Q7 \8 Ntmp_web_1 exited with code 1
/ J$ D+ m3 E9 K: A, }database_1  | sleep over  V0 B$ V1 Y% M# t  T$ v9 `" s
问题解决方式 1.02 O0 J! ?% u& y8 @
修改 web 的启动脚本, 等待 database 的端口通了之后再启动服务  W9 Z& \# I5 j/ }

$ F2 Y; b" ]4 Q9 H' r6 yversion: '2'
; Y3 m! q' j2 N1 U  N9 @" Q  Cservices:; I! c) i( l' K: \1 p% _* l
  web:1 E: z7 Z- p4 N% O* ]
    image: ubuntu:14.04
: @  [/ l7 q; N    depends_on:6 Y! G( |% j0 Q2 ?8 P
      - database, G: I! K8 u; z/ P9 A
    command: >7 @6 }: E  r# [
      /bin/bash -c ', K# `& e5 O9 E! w& T5 e2 G' e
      while ! nc -z database 3306;% h3 Q! n! g2 B  `" A" o
      do
% Q2 U4 J8 o) d" r        echo "wait for database";
9 i. g7 W! o; S4 r* K5 R" H4 z        sleep 1;
% S- C4 e" @1 _) y( ?      done;
- r( D/ b1 Z; ]) _) N- d! M2 M3 ]
' s; G% e. c5 W1 t: B( q      echo "database is ready!";. d/ z3 ]8 ~7 a. {& T
      echo "start web service here";
* ?$ {+ f# [& e5 p3 ~& M      '
& r$ A# K4 g9 |( e8 U
: v9 T" Q  v3 {0 R2 i3 d  database:
6 Z% D9 A' i% S% J/ {+ f" V    image: ubuntu:14.04
/ v" N6 E$ @% e  v" z5 U: H& J& A4 B9 k    command: >5 M- q+ U7 E! z
      /bin/bash -c '9 ]- r- h, I- `5 d* b; ]0 y
      sleep 5;, `% u& _$ A! l0 c! ~% j
      echo "sleep over";1 x5 B$ v, Q  W0 u6 t* _
      nc -lk 0.0.0.0 3306;
. f) G8 Z% Z; H! F      '
6 Q  Z( t4 [& z; [再次启动,: C4 o" Z  f6 d3 O& y+ k( y& N
( Y2 O8 ]) t2 ^0 n0 n
$ docker-compose up
4 Q8 g- t  b# j9 O( kCreating tmp_database_1 ... done* ?3 y8 M) Z6 N  h8 Z
Creating tmp_database_1 ...
8 J8 U) p; ]3 `* T. S( dCreating tmp_web_1      ... done; s7 R8 \( X9 l0 v% [
Attaching to tmp_database_1, tmp_web_13 v9 O( B3 j6 o4 S
web_1       | wait for database
: b( }* l) H! C) q( Iweb_1       | wait for database
, x/ F& t/ O, l8 vweb_1       | wait for database, L( N! T( T- K6 C' h" R. A+ Q
web_1       | wait for database  M; y* q/ N, i
web_1       | wait for database
. A" N: m! m& d% Pdatabase_1  | sleep over( z2 I( m8 s/ k' A
web_1       | database is ready!7 R$ e/ ^* w/ r; f) j3 b3 u
web_1       | start web service here
) t4 }  g, L3 P  X3 k) g: a! S. xtmp_web_1 exited with code 0% V# F+ h/ B0 `, H( f
web 会在 database 启动完成, 端口通了之后才启动.% l2 H/ t; q' k4 [& n
6 C, J6 L' d) K0 V6 b  \
问题解决方式 2.0
. U" B* W4 t! u( h! ^上面的解决方式虽然能够解决问题, 但是在 yaml 中直接插入脚本不好维护, 也容易出错. 如果有多个依赖, 或者多层依赖的时候, 复杂度会直线上升.
/ w) I% E2 T% i8 K4 F' E6 g9 x7 b6 O* ^% H2 B/ n
所以, 要封装一个 entrypoint.sh 脚本, 可以接受启动命令, 以及需要等待的服务和端口. 脚本内容如下:
7 m; x) j; U) q( N
$ C. ^+ ~, J7 O& p- k# P: w#!/bin/bash
# C  S9 i) X# `' f/ \9 }2 T#set -x3 {4 {! {$ T" }, L; Q
#******************************************************************************
0 e- I$ p5 \: S) h& `: o# @File    : entrypoint.sh
' \% M3 G9 S& v8 h8 B# @author  : wangyubin$ ]+ g6 [& W9 e- S/ f  B
# @date    : 2018-08- 1 10:18:43$ o+ z$ g% V0 j# K& g2 Z
#
, w9 X2 N" k! p( s4 G1 p# @brief   : entry point for manage service start order
: t8 J; D! Y3 X1 q7 k# i3 P! N$ v# history  : init
8 x/ N2 F9 N& \& O  p6 I#******************************************************************************
( A' x! y: q+ S3 U+ C- G, M0 y" n& F( t: @. b$ `
: ${SLEEP_SECOND:=2}! x3 Y4 y  X, V$ D# V
* {& D3 `: m6 f( V1 ^( a% _9 t9 o7 q- L
wait_for() {' {" ?3 ?9 P- D
    echo Waiting for $1 to listen on $2...8 Y* x; e/ @6 e$ A0 g1 ?) g
    while ! nc -z $1 $2; do echo waiting...; sleep $SLEEP_SECOND; done
% t5 V1 L1 W  f9 ~) Q5 i2 [6 k}
8 M2 e; H4 q  c5 l: ~3 I1 ?% {1 E0 v+ l$ t
declare DEPENDS
/ R! x/ k6 P) @declare CMD
: r9 o1 Y* w: L* \  c4 D# ?) I" u/ |' B* {
while getopts "d:c:" arg
% }1 O9 f+ ~; mdo3 b2 U% e$ q" _* u
    case $arg in1 k$ r- l+ R) R: M
        d)' ~: u7 o1 F. ?* C9 u
            DEPENDS=$OPTARG
) ?" W3 h9 _6 D  a            ;;
2 Q- J& U$ I3 Q' t        c)2 U  u; y1 h7 n" `' S- x% d
            CMD=$OPTARG
. {& ?6 |- U" _3 X9 w2 p            ;;2 s) `6 c" o, u$ s! _1 y
        ?)
/ Q& D" P8 {' W% K6 Z; z( k( l6 g            echo "unkonw argument"
6 \, M$ m% {. K% K6 q            exit 10 W# L9 z) Y" B, z% j% L+ u
            ;;
) q8 u& I5 N# S! Z0 Z5 J    esac
4 C0 Q" o7 q' @% t0 b+ ndone
) U. Y1 H8 T7 m: y3 W. d& N/ {5 s5 e' S" ?
for var in ${DEPENDS//,/ }8 e% s+ T3 [: v; n$ d
do2 S2 O2 i' ]- }4 C3 w2 \
    host=${var%:*}6 x  }$ @. z9 u# D
    port=${var#*:}
  h4 c0 ~+ Q% C    wait_for $host $port' ]- W0 \/ Y/ d, ^) u
done
* S& i/ u% I9 v1 l7 }8 B$ y+ O' n) o: T5 x8 m; l! S; _0 t3 {1 s
eval $CMD/ l1 ?% }& M+ j% Y$ {
这个脚本有 2 个参数, -d 需要等待的服务和端口, -c 等待的服务和端口启动之后, 自己的启动命令; M! K; l& V9 U' @& ~
/ W% c* I! o4 i
修改 docker-compose.yml, 使用 entrypoint.sh 脚本来控制启动顺序.4 W, x$ W) s# v" z7 R( R
. \+ ?3 q* O7 J: p: e5 X
version: '2'( e. ]2 k: c+ R# a: w+ E
services:
" e/ @0 f+ o* d+ E  web:
. w$ X7 S& O/ m' e1 j! R    image: ubuntu:14.04
$ q- Q  S2 C! r" R4 b2 D    depends_on:/ D  H2 u* @0 v) L! v. c5 W: ]* b. B
      - database
# L$ J5 X1 L& M* V" X2 f) Z    volumes:* ?7 i8 V0 e9 b
      - "./entrypoint.sh:/entrypoint.sh"9 U' V* r, j2 f! m4 {3 y5 @. j
    entrypoint: /entrypoint.sh -d database:3306 -c 'echo "start web service here"';
8 _0 g$ ?% t- i1 M5 A2 [
( r$ |' @& g' U1 O  database:& V( a" ?& [5 t) l( k( V3 a) J
    image: ubuntu:14.04
8 v' n( ]: h/ N: X8 o    command: >* u; e5 n8 Q0 J" F" f5 p8 a# v7 k
      /bin/bash -c '- s2 ?1 }; v# I; X) _
      sleep 5;: }# ]2 E! r, c, j& I
      echo "sleep over";
3 C' c% e9 ~( j6 j& R2 N+ \      nc -lk 0.0.0.0 3306;, X% Z% u( S8 J  `
      '8 p+ C5 d/ G5 q+ A' h6 ]: U8 C
实际使用中, 也可以将 entrypoint.sh 打包到发布的镜像之中, 不用通过 volumes 配置来加载 entrypoint.sh 脚本.9 J0 z) Y" K4 c

' H7 c! Y) l9 c6 }  c9 p- J6 {: W) o& }测试结果如下:8 A' y3 K+ N# C3 {; ?- n

& u5 X2 y* i9 E- S* H- q) X$ docker-compose up( ?5 J% \$ ]1 r0 M
Starting tmp_database_1 ... done! t% l+ o0 I* D7 E
Starting tmp_web_1 ... done
' q5 W5 }1 F7 B! U" \/ e: {2 `5 vAttaching to tmp_database_1, tmp_web_11 p* D" k: P  ?0 b9 f4 p+ `9 u
web_1       | Waiting for database to listen on 3306...  T7 f: T$ i8 d* v* f- h" Q
web_1       | waiting...
, V  T3 @3 I5 E8 ?' M+ aweb_1       | waiting...
% {' d! b& V1 Aweb_1       | waiting...
6 r6 ?0 Q& S3 P) i/ Odatabase_1  | sleep over
1 V, O; S- R/ y3 |web_1       | start web service here! h" I# e# ~* ]* T
tmp_web_1 exited with code 0
/ \* @7 l% z& B+ m! c补充+ S7 g, Q& c2 e# x& y1 U
依赖多个服务和端口7 ^5 S* Q) m6 y/ H$ P7 _
使用上面的 entrypoint.sh 脚本, 也可以依赖多个服务和端口, -d 参数后面的多个服务和端口用逗号(,)隔开.
0 q5 B! d) H/ L1 }+ e5 K7 s/ q9 S
version: '2'
! f/ v  W; C. N+ `, x9 {services:: I& _8 F, Y3 K! U: O
  web:% Z: o( \4 J2 ?+ ^8 d
    image: ubuntu:14.042 f4 D) P6 l5 Y* V
    depends_on:
  F& K9 r( D3 Y( a! g      - mysql
( s3 w( J" x1 s7 _3 |6 d      - postgresql6 b" ^  {! z5 r
    volumes:1 G: ?, |# k6 T# r  O6 h& L, U
      - "./entrypoint.sh:/entrypoint.sh". Z1 ^$ W) Y# {& m. X
    entrypoint: /entrypoint.sh -d mysql:3306,postgresql:5432 -c 'echo "start web service here"';1 C- W2 l5 e9 a! C4 \/ o

  B( ]/ \6 A, w! |  mysql:
: a  n: z, }  w, m. h    image: ubuntu:14.044 e# v. R/ _8 N* K
    command: >
% _5 w/ v) b1 g6 |& A# H2 v+ \      /bin/bash -c '
+ Z4 \" |6 z) s4 B' M/ s" I      sleep 4;- H+ \6 k1 P8 c1 K9 Z) C* f
      echo "sleep over";
; E. ^7 ~" G! M+ m' ^% F  ?      nc -lk 0.0.0.0 3306;' x. O3 V; S! v; ^5 D3 J. A
      '
# u0 I( r' q6 S0 }/ v+ i  postgresql:6 A+ L0 o- w0 k! d9 H# R
    image: ubuntu:14.04
5 ~: R. j' W5 O3 K; d& e    command: >
7 _! r. Q' W" Q) |# O      /bin/bash -c '8 K8 j0 i2 x. j4 x. g+ V6 t
      sleep 8;9 T' M( P$ U% `; H6 v. |
      echo "sleep over";
) P1 ?' ]) T- ]  e5 D! X/ ~      nc -lk 0.0.0.0 5432;0 N8 M, b/ p' g& w  T
      '
: U3 p& o3 i; C$ X执行的效果可以自行尝试./ R) p/ E0 P* _
5 N- M+ W. }! W- k: L
尝试间隔的配置
# j9 e) a+ X& i5 L% M每次尝试连接的等待时间可以通过 环境变量 SLEEP_SECOND 来配置, 默认 2 秒 下面的配置等待时间设置为 4 秒, 就会每隔 4 秒才去尝试 mysql 服务时候可连接.
+ s1 ^" C- O9 ]# h4 p/ O3 v! ?7 D6 p$ \3 E8 {
version: '2'
3 m; x% @; |. F$ b5 H* H8 [services:
  W: U) V& ?- p+ d2 Y  web:7 y% E3 ]. {7 `0 D0 U% [+ ]2 E
    image: ubuntu:14.04# c. I$ ?  W' X3 Z, g
    environment:1 \3 a% H5 s3 q& D; @
      SLEEP_SECOND: 4) s; B% l, x3 }$ m; C3 f, ^
    depends_on:9 ]$ ~3 S$ c6 M% I7 s+ L
      - mysql
7 u' \' h& s% S' B2 ^# Z    volumes:6 a2 R' ?' h! N
      - "./entrypoint.sh:/entrypoint.sh"! e  ]. B; A& J, y
    entrypoint: /entrypoint.sh -d mysql:3306 'echo "start web service here"';1 \7 l- j( A# h  x2 P3 h7 I% P+ L
6 [1 b0 C- Q# c5 E
  mysql:
3 J" O1 j$ M2 q  p8 w    image: ubuntu:14.04, ~( S0 X* V" T5 B6 @
    command: >. o0 {1 q/ N3 T3 ]+ k
      /bin/bash -c '
' \* S8 j. c" f; `" \      sleep 4;# z/ Y1 q6 X5 Z  G
      echo "sleep over";/ `# s( A/ T/ k' a+ z) b
      nc -lk 0.0.0.0 3306;
作者: regngfpcb    时间: 2020-8-10 18:05
docker compose 服务启动顺序控制




欢迎光临 EDA365电子论坛网 (https://bbs.eda365.com/) Powered by Discuz! X3.2