Docker Compose介紹及使用入門

一、compose介紹

在先前介紹Docker的內(nèi)容中,我們從代碼到部署容器需要經(jīng)過兩個步驟:

  • 打包鏡像docker build洋侨;
  • 部署啟動容器docker run

在真實的企業(yè)級應用中倦蚪,一個服務往往需要和很多個其它的服務進行關(guān)聯(lián)希坚,單個服務也有可能有多個容器實例,如果需要發(fā)布陵且,很可能會需要人員手動對每一個容器進行打包和啟動的操作裁僧,非常繁瑣,容易出錯。

在這種背景下聊疲,Docker Compose就有了用武之地茬底。簡單來說,Docker Compose是一個用于定義和運行多個容器的工具获洲,通過docker-compose.yml來實現(xiàn)對容器集群的編排工作桩警。

Docker Compose管理著如下三個內(nèi)容:

  • 工程,docker-compose運行的目錄即為一個工程昌妹,在微服務場景下,我們往往都是使用git submodule的方式組建工程的握截,因此父項目就可以成為一個docker compose的工程飞崖;
  • 服務,對應子項目谨胞,一個工程可以包含多個子項目固歪;
  • 容器,對應服務的實例胯努,一個服務可以有多個實例牢裳;

Docker Compose當然也存在不足的地方,就是它只能用在單一host上進行容器編排叶沛,無法跨節(jié)點host對容器進行編排蒲讯,那是Docker Swarm和K8s的范疇了,后續(xù)再討論灰署。

二判帮、compose使用

2.1 單服務單容器使用

我們新建一個SpringBoot應用,僅僅包含一個Controller:

@Slf4j
@RestController
public class HelloController {

    @GetMapping("/getHello")
    public String getHello(){
        log.info("myapp works!");
        return "myapp is running ok!!!";
    }

}

請務必保證程序能正常運行溉箕,再進行如下操作晦墙。并進行package,打成jar包肴茄。

編寫Dockerfile:

FROM openjdk:8
EXPOSE 8080
ADD  target/myapp-0.0.1-SNAPSHOT.jar /demo.jar
ENTRYPOINT ["java", "-jar", "demo.jar"]

編寫docker-compose.yml文件:

# 使用的yml版本
version: "3.9"
services:
  # 服務名稱晌畅,可以自定義
  myapp:
    # 容器名稱,可以自定義
    container_name: myapp
    # 指定Dockerfile所在的目錄
    build: .
    ports:
      - "8080:8080"

然后執(zhí)行docker-compose up即可寡痰,主要完成以下的兩步操作:

  • 鏡像構(gòu)建docker build抗楔;
  • 啟動yml中的所有容器docker run

執(zhí)行過程如下:

E:\myapp>docker-compose up
# 創(chuàng)建了默認類型的自定義網(wǎng)絡氓癌,即bridge類型網(wǎng)絡谓谦,而非使用默認的docker0橋接網(wǎng)絡,擁有自己的獨立網(wǎng)段贪婉,可以通過docker network ls及docker network inspect查看具體的網(wǎng)絡信息
Creating network "myapp_default" with the default driver
Building myapp
[+] Building 0.5s (7/7) FINISHED
 => [internal] load build definition from Dockerfile                                                                                 0.0s
 => => transferring dockerfile: 153B                                                                                                 0.0s
 => [internal] load .dockerignore                                                                                                    0.0s
 => => transferring context: 2B                                                                                                      0.0s
 => [internal] load metadata for docker.io/library/openjdk:8                                                                         0.0s
 => [internal] load build context                                                                                                    0.2s
 => => transferring context: 17.62MB                                                                                                 0.1s
 => CACHED [1/2] FROM docker.io/library/openjdk:8                                                                                    0.0s
 => [2/2] ADD  target/myapp-0.0.1-SNAPSHOT.jar /demo.jar                                                                             0.1s
 => exporting to image                                                                                                               0.1s
 => => exporting layers                                                                                                              0.1s
 # 將鏡像寫入本地的鏡像倉庫反粥,并以項目名稱_服務名稱命名鏡像
 => => writing image sha256:c387978706931f09fa16a737704f2c1047e8f632de192a25b0dc42dc151ac4c7                                         0.0s
 => => naming to docker.io/library/myapp_myapp                                                                                       0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
WARNING: Image for service myapp was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating myapp ... done
Attaching to myapp
myapp    |
myapp    |   .   ____          _            __ _ _
myapp    |  /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
myapp    | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
myapp    |  \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
myapp    |   '  |____| .__|_| |_|_| |_\__, | / / / /
myapp    |  =========|_|==============|___/=/_/_/_/
myapp    |  :: Spring Boot ::                (v2.6.0)
......

到此,我們的單容器使用方式完成了。

如下是一些常見的docker-compose操作(需要在工程目錄下執(zhí)行命令):

  • docker-compose up才顿,構(gòu)建鏡像并啟動容器莫湘;

  • docker-compose down,停止容器郑气,刪除容器幅垮,移除自定義網(wǎng)絡;

    E:\myapp>docker-compose down
    Stopping myapp ... done
    Removing myapp ... done
    Removing network myapp_default
    
  • docker-compose ls尾组,查看所有運行的容器忙芒;

    E:\myapp>docker-compose ps
    Name         Command         State           Ports
    -----------------------------------------------------------
    myapp   java -jar demo.jar   Up      0.0.0.0:8080->8080/tcp
    
  • docker-compose logs -f container_name,查看具體容器的日志讳侨,-f參數(shù)表示實時日志輸出呵萨;

  • docker-compose port container_name container_port,查看和容器端口綁定的主機端口跨跨;

  • docker-compose stop container_name潮峦,停止指定的容器,如果不指定則停止所有的容器勇婴;

  • docker-compose start container_name忱嘹,啟動指定的容器,如果不指定則停止所有的容器耕渴;

  • docker-compose rm container_name拘悦,刪除指定的已停止容器,如果不指定則刪除所有已停止容器橱脸;

  • docker-compose build窄做,構(gòu)建或者重新構(gòu)建服務的鏡像,但不會創(chuàng)建和啟動容器慰技;

2.2 多服務多容器依賴使用

假設我們的應用需要依賴其它服務椭盏,比如需要使用redis,mysql等吻商,那么這種場景下掏颊,就需要被依賴的容器先啟動。

首先艾帐,我們改造上述例子中的myapp代碼乌叶,需要引入redis的支持依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>       
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

然后增加redis的配置,容器啟動的默認redis是沒有密碼的柒爸,所以不用配置password准浴。

server:
  port: 8080
spring:
  redis:
    host: 127.0.0.1
    port: 6379
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0

再增加redis的序列化和反序列化的配置:

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfig {

    /**
     * 配置自定義redisTemplate
     * @return
     */
    @Bean
    RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        //使用Jackson2JsonRedisSerializer來序列化和反序列化redis的value值
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);

        template.setValueSerializer(serializer);
        //使用StringRedisSerializer來序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();
        return template;
    }

}

再后,我們需要修改Controller的邏輯捎稚,使得返回的結(jié)果依賴redis:

@Slf4j
@RestController
public class HelloController {

    @Autowired
    private RedisTemplate redisTemplate;

    @GetMapping("/getHello")
    public String getHello(){
        log.info("myapp works!");
        Long counter = redisTemplate.opsForValue().increment("counter");
        return "myapp is running " + counter + "times!";
    }

}

如此乐横,每次訪問該接口都會使得計數(shù)器加1并返回結(jié)果求橄。

最后,我們只需要修改docker-compose.yml

version: "3.9"
services:
  myapp:
    container_name: myapp
    build: .
    ports:
      - "8080:8080"
    depends_on:
      - myredis
  myredis:
    image: "redis:latest"

其它內(nèi)容不變葡公,如此配置就全部完成了罐农,注意在執(zhí)行如下操作之前,先確保程序能夠正常運行催什,可以先自行運行一個redis容器做下實驗涵亏。

docker-compose up啟動工程,過程如下:

......
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
WARNING: Image for service myapp was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating myapp           ... done
Creating myapp_myredis_1 ... done
Attaching to myapp_myredis_1, myapp
myredis_1  | 1:C 21 Nov 2021 03:19:06.934 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
myredis_1  | 1:C 21 Nov 2021 03:19:06.934 # Redis version=6.2.6, bits=64, commit=00000000, modified=0, pid=1, just started
myredis_1  | 1:C 21 Nov 2021 03:19:06.934 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
myredis_1  | 1:M 21 Nov 2021 03:19:06.935 * monotonic clock: POSIX clock_gettime
myredis_1  | 1:M 21 Nov 2021 03:19:06.936 * Running mode=standalone, port=6379.
myredis_1  | 1:M 21 Nov 2021 03:19:06.936 # Server initialized
myredis_1  | 1:M 21 Nov 2021 03:19:06.936 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the
command 'sysctl vm.overcommit_memory=1' for this to take effect.
myredis_1  | 1:M 21 Nov 2021 03:19:06.936 * Ready to accept connections
myapp      |
myapp      |   .   ____          _            __ _ _
myapp      |  /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
myapp      | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
myapp      |  \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
myapp      |   '  |____| .__|_| |_|_| |_\__, | / / / /
myapp      |  =========|_|==============|___/=/_/_/_/
myapp      |  :: Spring Boot ::                (v2.6.0)
......

此處的redis是使用的已有鏡像蒲凶,所以不會再創(chuàng)建redis的鏡像气筋,但是myapp是需要build構(gòu)建的,所以需要創(chuàng)建myapp的鏡像旋圆,然后再基于這倆個鏡像分別創(chuàng)建兩個容器裆悄,這兩個容器都屬于myapp這個工程下面。

2.3 多服務多容器獨立使用

除了如上依賴容器的使用臂聋,日常開發(fā)中,我們都是使用git submodule的方式組織父工程和多個子工程或南,那么部署的時候就需要同時部署多個微服務子工程孩等。

我們重新新建一個SpringBoot的項目,名稱為demo采够,然后將工程下面的src刪除肄方,因為它將是一個父工程,然后新建兩個模塊service1和service2蹬癌,這兩個服務分別對外提供getHello的服務权她,service1端口設置8080,service2端口設置8081逝薪。

@Slf4j
@RestController
public class HelloRest {

    @GetMapping("/service1/getHello")
    public String getHello(){
        return "hello from service1";
    }

}
@Slf4j
@RestController
public class HelloRest {

    @GetMapping("/service2/getHello")
    public String getHello(){
        return "hello from service2";
    }

}

確保兩個子項目都能正常運行后再進行下面的步驟隅要。

執(zhí)行maven的package命令,確保兩個服務都生成了各自的jar董济,然后在各自的目錄內(nèi)新建Dockerfile:

FROM openjdk:8
EXPOSE 8080
ADD  target/service1-0.0.1-SNAPSHOT.jar /demo.jar
ENTRYPOINT ["java", "-jar", "demo.jar"]
FROM openjdk:8
EXPOSE 8081
ADD  target/service2-0.0.1-SNAPSHOT.jar /demo.jar
ENTRYPOINT ["java", "-jar", "demo.jar"]

然后在父工程目錄下新建docker-compose.yml

version: "3.9"
services:
  service1:
    container_name: service1
    # 指定Dockerfile的目錄
    build: ./service1
    ports:
      - "8080:8080"
  service2:
    container_name: service2
    # 指定Dockerfile的目錄
    build: ./service2
    ports:
      - "8081:8081"

然后可以執(zhí)行docker-compose up了步清,發(fā)現(xiàn)會新構(gòu)建兩個鏡像demo_service1和demo_service2,同時創(chuàng)建兩個容器并啟動虏肾。

Creating service1 ... done
Creating service2 ... done
Attaching to service1, service2

2.4 單服務多容器使用

我們在一開始講解docker-compose概念的時候廓啊,有提到過服務和容器之間的關(guān)系,即一個服務可以有多個容器封豪,但是在上面的例子中谴轮,我們都是一個服務一個容器的,那么想要實現(xiàn)一個服務啟動多個容器該怎么操作呢吹埠?

我們還是拿2.1節(jié)的例子作為演示第步,只要修改docker-compose.yml文件的內(nèi)容:

version: "3.9"
services:
  myapp:
    build: .
    ports:
      - "8080"

我們把container_name: myapp去掉了疮装,因為容器的名稱要求是唯一的,如果指定了名字雌续,那么哪個容器叫這個名字呢斩个?就不好區(qū)分了,去掉后驯杜,多個容器會使用工程名+服務名+數(shù)字進行自動命名受啥。

還有,需要把端口也改造為只指定容器的端口鸽心,不要指定host的端口滚局,這樣會自動綁定host上未使用的隨機端口。其實如果Dockerfile中指定了暴露的端口顽频,此處也可以不需要ports設置了藤肢。

到此,設置完畢糯景,執(zhí)行啟動命令myapp>docker-compose up --scale myapp=2嘁圈,就會啟動一個服務的兩個容器實例。

E:\myapp>docker-compose up --scale myapp=2
Creating network "myapp_default" with the default driver
Creating myapp_myapp_1 ... done
Creating myapp_myapp_2 ... done
Attaching to myapp_myapp_2, myapp_myapp_1
...
E:\myapp>docker-compose ps
    Name             Command         State            Ports
--------------------------------------------------------------------
myapp_myapp_1   java -jar demo.jar   Up      0.0.0.0:53425->8080/tcp
myapp_myapp_2   java -jar demo.jar   Up      0.0.0.0:53424->8080/tcp

當然蟀淮,這兩個容器沒有實現(xiàn)負載均衡最住,這個不在本文討論范圍內(nèi)了,可以參考另外一篇文章《Nginx使用入門及實例演示》怠惶。

三涨缚、參考文檔

使用Docker compose發(fā)布SpringBoot項目-阿里云開發(fā)者社區(qū) (aliyun.com)

Overview of Docker Compose | Docker Documentation

Compose file version 3 reference | Docker Documentation

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市策治,隨后出現(xiàn)的幾起案子脓魏,更是在濱河造成了極大的恐慌,老刑警劉巖通惫,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茂翔,死亡現(xiàn)場離奇詭異,居然都是意外死亡履腋,警方通過查閱死者的電腦和手機檩电,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來府树,“玉大人俐末,你說我怎么就攤上這事⊙傧溃” “怎么了卓箫?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長垄潮。 經(jīng)常有香客問我烹卒,道長闷盔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任旅急,我火速辦了婚禮逢勾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘藐吮。我一直安慰自己溺拱,他們只是感情好,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布谣辞。 她就那樣靜靜地躺著迫摔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪泥从。 梳的紋絲不亂的頭發(fā)上句占,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機與錄音躯嫉,去河邊找鬼纱烘。 笑死,一個胖子當著我的面吹牛祈餐,可吹牛的內(nèi)容都是我干的擂啥。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼昼弟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了奕筐?” 一聲冷哼從身側(cè)響起舱痘,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎离赫,沒想到半個月后芭逝,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡渊胸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年旬盯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片翎猛。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡胖翰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出切厘,到底是詐尸還是另有隱情萨咳,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布疫稿,位于F島的核電站培他,受9級特大地震影響鹃两,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜舀凛,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一俊扳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧猛遍,春花似錦馋记、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至奸晴,卻和暖如春冤馏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背寄啼。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工逮光, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人墩划。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓涕刚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親乙帮。 傳聞我的和親對象是個殘疾皇子杜漠,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容