Dzisiaj, krótki wpis na temat powtarzania treści w docker-compose.

Czy wiesz, że zasadę DRY można zastosować również w pliku docker-compose? Nie jest to jednak mechanizm samego dockera a języka YAML. Mowa tutaj o funkcjach anchor oraz merge.

Jak to wygląda w praktyce? Wyobraźmy sobie, że nasze środowisko lokalne nie jest unikalne dla każdego projektu, a po prostu mamy 1 plik docker-compose, który uruchamia nam serwer http, bazę danych i kilka wersji PHP. Projekty muszą się komunikować ze sobą, a wszystkie logi zapisujemy do grayloga. Jak może wyglądać wtedy plik docker-compose?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
version: '3.7'

services:
    mysql:
        hostname: mysql
        image: mysql:5.7
        volumes:
            - mysql_volume:/var/lib/mysql
        ports:
            - 3306:3306
        environment:
            - MYSQL_ROOT_PASSWORD=docker
            - MYSQL_DATABASE=db
            - MYSQL_USER=user
            - MYSQL_PASSWORD=password
        networks:
            mynet:
        logging:
            driver: gelf
            options:
                gelf-address: "udp://1.2.3.4:12201"
    nginx:
        hostname: nginx
        build:
            context: docker/nginx
            args:
                - WWW_DATA_UID=${WWW_DATA_UID}
        ports:
            - 8080:80
        volumes:
            - ~/.composer:/var/www/.composer:delegated
            - ../project_one:/var/www/app/project_one
            - ../project_two:/var/www/app/project_two
        networks:
            mynet:
                ipv4_address: 172.26.0.6
        logging:
            driver: gelf
            options:
                gelf-address: "udp://1.2.3.4:12201"
    php73:
        hostname: php73
        build:
            context: docker/php/php73-fpm
            args:
                - WWW_DATA_UID=${WWW_DATA_UID}
        volumes:
            - ~/.composer:/var/www/.composer:delegated
            - ../project_one:/var/www/app/project_one
            - ../project_two:/var/www/app/project_two
        extra_hosts:
            - "project-one.local:172.26.0.6"
            - "project-two.local:172.26.0.6"
        networks:
            mynet:
        logging:
            driver: gelf
            options:
                gelf-address: "udp://1.2.3.4:12201"
    php74:
        hostname: php74
        build:
            context: docker/php/php74-fpm
            args:
                - WWW_DATA_UID=${WWW_DATA_UID}
        volumes:
            - ~/.composer:/var/www/.composer:delegated
            - ../project_one:/var/www/app/project_one
            - ../project_two:/var/www/app/project_two
        extra_hosts:
            - "project-one.local:172.26.0.6"
            - "project-two.local:172.26.0.6"
        networks:
            mynet:
        logging:
            driver: gelf
            options:
                gelf-address: "udp://1.2.3.4:12201"

networks:
    mynet:
        driver: bridge
        ipam:
            config:
              - subnet: 172.26.0.0/24

volumes:
    mysql_volume:

Sporo powtórzeń prawda? 4 kontenery, a prawie 90 linii kodu. Jednak duża objętość to nie wszystkie minusy. W sytuacji, kiedy będziemy chcieli dodać kolejny projekt, musimy zrobić zmianę w wolumenach oraz hostach. Musimy więc edytować volume w trzech kontenerach i extra_hosts w dwóch.

Anchor i Merge

Z pomocą przychodzą nam funkcję anchor oraz merge. Jak one działają? Zobaczmy to na poniższych przykładach. Lewa kolumna to zapis skrócony, a prawa to wynik co widzi parser:

1
2
3
4
5
anchor: &app
key_one: 1
key_two: value

merge: *app
1
2
3
4
5
6
7
anchor:
    key_one: 1
    key_two: value

merge:
    key_one: 1
    key_two: value

Oba klucze będą miały taką samą treść – key_one oraz key_two. Możemy łączyć więcej niż 1 element w całość:

1
2
3
4
5
6
7
8
anchor_one: &app
  key_one: 1

anchor_two: &app2
  key_two: value

merge:
  <<: [*app, *app2]
1
2
3
4
5
6
7
8
9
anchor_one:
  key_one: 1

anchor_two:
  key_two: value

merge:
  key_one: 1
  key_two: value

Możemy również nadpisywać wartości z kotwic

1
2
3
4
5
6
7
anchor: &app
  key_one: 1
  key_two: value

merge:
  <<: *app
  key_two: value2
1
2
3
4
5
6
7
anchor:
  key_one: 1
  key_two: value

merge:
  key_one: 1
  key_two: value2

Połączmy wszystko w całość Skoro teorię mamy już za sobą, to sprawdźmy jak możemy zmienić przykładowy docker-compose, aby nie zawierał zbędnych powtórzeń. Z powtarzających się elementów mamy: volume, network, logging oraz extra_hosts.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
version: '3.7'

x-default_hosts: &hosts
  extra_hosts:
    - "project-one.local:172.26.0.6"
    - "project-two.local:172.26.0.6"

x-default_service: &default_service
  networks:
    mynet:
  logging:
    driver: gelf
    options:
      gelf-address: "udp://1.2.3.4:12201"

x-default_volumes: &appvolumes
  volumes:
    - ~/.composer:/var/www/.composer:delegated
    - ../project_one:/var/www/app/project_one
    - ../project_two:/var/www/app/project_two

services:
  mysql:
    <<: *default_service
    hostname: mysql
    image: mysql:5.7
    volumes:
      - mysql_volume:/var/lib/mysql
    ports:
      - 3306:3306
    environment:
      - MYSQL_ROOT_PASSWORD=docker
      - MYSQL_DATABASE=db
      - MYSQL_USER=user
      - MYSQL_PASSWORD=password
  nginx:
    <<: [*default_service, *appvolumes]
    hostname: nginx
    build:
      context: docker/nginx
      args:
        - WWW_DATA_UID=${WWW_DATA_UID}
    ports:
      - 8080:80
    networks:
      mynet:
        ipv4_address: 172.26.0.6
  php73:
    <<: [*default_service, *appvolumes, *hosts]
    hostname: php73
    build:
      context: docker/php/php73-fpm
      args:
        - WWW_DATA_UID=${WWW_DATA_UID}
  php74:
    <<: [*default_service, *appvolumes, *hosts]
    hostname: php74
    build:
      context: docker/php/php74-fpm
      args:
        - WWW_DATA_UID=${WWW_DATA_UID}

networks:
  mynet:
    driver: bridge
    ipam:
      config:
        - subnet: 172.26.0.0/24

volumes:
  mysql_volume:

Z 88 linii zmniejszyliśmy kod do 71, ale nie to jest najważniejsze. Zyskaliśmy czytelność i łatwość zmian poszczególnych fragmentów kodu.

Kod, który tutaj opisałem jest możliwy do osiągnięcia od wersji pliku 3.4. Jeżeli używasz niższej wersji, możesz również użyć anchor oraz merge, jednak nie możesz zdefiniować własnych obiektów, ponieważ docker-compose na to nie pozwala. Możesz to obejść stosując kotwice w definicji serwisu i wykorzystać ją w kolejnym:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
version: '3.7'

services:
  mysql:
    hostname: mysql
    image: mysql:5.7
    volumes:
      - mysql_volume:/var/lib/mysql
    ports:
      - 3306:3306
    environment:
      - MYSQL_ROOT_PASSWORD=docker
      - MYSQL_DATABASE=db
      - MYSQL_USER=user
      - MYSQL_PASSWORD=password
    networks: &appnetwork
      mynet:
    logging: &applogging
      driver: gelf
      options:
        gelf-address: "udp://1.2.3.4:12201"
  nginx:
    hostname: nginx
    build:
      context: docker/nginx
      args:
        - WWW_DATA_UID=${WWW_DATA_UID}
    ports:
      - 8080:80
    logging: *applogging
    volumes: &appvolumes
      - ~/.composer:/var/www/.composer:delegated
      - ../project_one:/var/www/app/project_one
      - ../project_two:/var/www/app/project_two
    networks:
      mynet:
        ipv4_address: 172.26.0.6
  php73:
    hostname: php73
    build:
      context: docker/php/php73-fpm
      args:
        - WWW_DATA_UID=${WWW_DATA_UID}
    volumes: *appvolumes
    logging: *applogging
    networks: *appnetwork
    extra_hosts: &apphosts
      - "project-one.local:172.26.0.6"
      - "project-two.local:172.26.0.6"
  php74:
    hostname: php74
    build:
      context: docker/php/php74-fpm
      args:
        - WWW_DATA_UID=${WWW_DATA_UID}
    volumes: *appvolumes
    logging: *applogging
    networks: *appnetwork
    extra_hosts: *apphosts

networks:
  mynet:
    driver: bridge
    ipam:
      config:
        - subnet: 172.26.0.0/24

volumes:
  mysql_volume:

Kodu jest trochę mniej, ale według mnie ucierpiała tutaj czytelność i łatwość dodania nowych funkcji do wszystkich kontenerów na raz.

Daj znać w komentarzu, czy podobał Ci się artykuł. Daj to niesamowitej energii do pisania kolejnych treści. Sprawdź również mój wcześniejszy artykuł na temat optymalizacji docker for mac.