Jeśli również pracujesz na Macu i masz problem z wolno działającym Docker for Mac, to mam dla Ciebie kilka tricków, które przyspieszą działanie Twojego Dockera bez dodatkowych narzędzi.

Ale na początek, jak wyglądają czasy jednego z projektów nad którym pracuje. Zobaczmy więc docker-compose. Możemy w nim zauważyć montowanie wolumenu do kontenera nginx i nadanie mu aliasu, który wykorzystujemy w kontenerze PHP.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
version: '2'
services:
    nginx:
        hostname: nginx
        build:
            context: docker/nginx
        ports:
            - 8080:80
        volumes: &appvolumes
            - ../:/var/www/app
        networks:
            mynet:
                ipv4_address: 172.26.0.1
    php74:
        hostname: php74
        build:
            context: docker/php/php74-fpm
        volumes: *appvolumes
        networks:
            mynet:
                
ipv4_address: 172.26.0.2 

Jak sami widzicie, czasy są nie do zaakceptowania. A jest to tylko jedna prosta podstrona aplikacji z wykorzystaniem Symfony.

Co możemy w takiej sytuacji zrobić? Sprawdźmy jak będą wyglądały czasy po wprowadzeniu kilku poprawek wydajnościowych.

1. Używaj NFS

Po pierwsze wykorzystaj NFS do synchronizacji danych między hostem lokalnym a kontenerem. Przyspieszy to działanie dockera.

  1. Pobieramy plik z gist – https://gist.github.com/seanhandley/7dad300420e5f8f02e7243b7651c6657#file-setup_native_nfs_docker_osx-sh
  2. Uruchamiamy pobrany wcześniej skrypt
  3. Modyfikujemy docker-compose
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
version: '2'

volumes:
    nfsmount:
        driver: local
        driver_opts:
            type: nfs
            o: addr=host.docker.internal,rw,nolock,hard,nointr,nfsvers=3
            device: "/absolute/path/to/project/dir"
services:
    nginx:
        hostname: nginx
        build:
            context: docker/nginx
        ports:
            - 8080:80
        volumes: &appvolumes
            - nfsmount:/var/www/app
    ...
  1. Uruchamiamy kontener

Jak to wygląda teraz dla tej samej strony? Na pewno duże lepiej, ale nadal nie jest idealnie.

1340 ms - czas pierwszego załadowania strony

Pierwsze załadowanie po zmianach w kodzie

354ms - czas odświeżenia strony

Kolejne załadowanie strony

2. Wyrzuć niepotrzebne sprawdzanie katalogów

Docker for Mac domyślnie ma ustawione kilka głównych katalogów, które za każdym razem sprawdza czy, któryś plik się nie zmienił. Jeżeli używamy NFS’a jest to zbędna operacja, która tylko zajmuje czas. Nie potrzebujemy, aby docker zajmował się sprawdzaniem i synchronizacją plików między hostem a kontenerem, ponieważ mamy od tego NFS.

Wchodzimy więc w ustawienia Docker for Mac > Resources > File sharing, a następnie usuwamy wszystkie pozycje. Jeżeli jednak potrzebujecie synchronizować jakiś katalog bez użycia NFS, wybierzcie jego ścieżkę. Dzięki temu nadal będziecie mogli wykorzystywać synchronizację plików, która zapewnia Docker, a jednocześnie przyspieszycie aplikację.

Uwaga, wykorzystanie tego kroku, praktycznie eliminuje używanie docker run, a co za tym idzie, również nie skorzystamy w dockera w naszym IDE. Wszystko będziemy musieli uruchamiać w działającym kontenerze via CLI.

Sprawdźmy jak teraz działa nasza aplikacja.

1025 ms - czas pierwszego załadowania strony

Pierwsze załadowanie po zmianach

193ms - czas odświeżenia strony

Kolejne załadowanie strony

Na prostej stronie nie widzimy zbyt dużej różnicy. Jednak przy większych projektach, ta różnica potrafi być spora.

3. Wersja Edge – mutagen.io

Najnowsza wersja Docker for Mac w wersji Edge wprowadziła możliwość synchronizacji plików przy pomocy mutagen.io. Więcej informacji znajdziesz tutaj: klik. Według mnie będzie to game changer dla osób pracujących na Macu. Dlaczego będzie skoro zostało już wprowadzone? Po pierwsze jest to dopiero w wersji Edge, a po drugie ma jeszcze sporo błędów i widać jedynie Error na liście File Sharing. Błędy, które wystąpiły możemy zobaczyć odpytując lokalne API Dockera:

1
curl -X GET --unix-socket ~/Library/Containers/com.docker.docker/Data/docker-api.sock http://localhost/cache/state | jq

Implementacja mutagena a Dockerze ma problem z linkami absolutnymi w aplikacji. To niestety wyklucza użycie phpunit w symfony:

1
2
3
4
5
6
7
8
9
{
    "/Users/grzegorz/dev/project": {
        "status": "Error",
        "ready": false,
        "problems": [
            "beta scan error: remote error: invalid symbolic link (project/vendor/bin/.phpunit/phpunit-5.7.27/vendor/symfony/phpunit-bridge): target is absolute"
        ]
    }
}

Mimo wszystko sprawdźmy jak zachowuje się nasza aplikacja po włączeniu mutagena.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
version: '2'

services:
    nginx:
        hostname: nginx
        build:
            context: docker/nginx
        ports:
            - 8080:80
        volumes: &appvolumes
            - ../:/var/www/app
    ...
150 ms - czas pierwszego załadowania strony

Pierwsze odświeżenie po zmianach w kodzie

99ms - czas odświeżenia strony

Kolejne załadowanie

Wygląda to naprawę dobrze. Ale… Dodajmy jeszcze volumen na cache

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
version: '2'

volumes:
    app_cache:

services:
    nginx:
        hostname: nginx
        build:
            context: docker/nginx
        ports:
            - 8080:80
        volumes: &appvolumes
            - ../:/var/www/app
            - app_cache:/var/www/app/project/var/cache
    ...
108 ms - czas pierwszego załadowania strony

Pierwsze załadowanie po zmianach w kodzie

85ms - czas odświeżenia strony

Kolejne odświeżenie strony


Praca z dockerem na Mac OSX wcale nie musi być utrapieniem. Jeżeli znasz jeszcze inne tricki jak zoptymalizować dockera, koniecznie daj znać w komentarzu.

P.S. Jeżeli będziesz chciał przejść na Docker Edge, pamiętaj, że pierwsze uruchomienie Docker Edge wyczyści wszystkie obrazy, kontenery oraz wolumeny!! Jeżeli masz kontenery z danymi, których nie chciał byś wyczyścić, zrób koniecznie ich backup.