Образ (image) - основа для запуска контейнеров. Образы хранятся в реестрах (registries) - публичных (типа hub.docker.com) или приватных, т. е., можно развернуть свой.
# Добавить пользователя в группу Docker, чтобы он имел там все права: sudo usermod -aG docker username newgrp docker # чтобы не перезапускать машину # Запустить контейнер с Ubuntu и зайти в его терминал docker run -it ubuntu bash # Список всех контейнеров, в т. ч. остановленных docker ps -a # Запустить остановленный контейнер docker start containername # Остановить docker stop containername # Запуск с указанием имени хоста docker run -h hostname -it ubuntu bash # Список запущенных контейнеров docker ps # Информация о контейнере docker inspect containername # Задать имя контейнера docker run --name containername -it ubuntu bash # Посмотреть изменения в контейнере docker diff containername # Журнал контейнера docker logs containername # Удалить контейнер docker rm containername # Удалить все остановленные контейнеры docker rm -v $(docker ps -aq -f status=exited) # Удалить все образы docker rmi $(docker images -q) --force # Запустить в фоне с пробросом порта хост:контейнер docker run -d -p 8000:8080 apache # Список образов docker images # Создать образ из контейнера docker commit containername username/imagename # Закачать образ в реестр-репозиторий # Тэг можно не указывать, тогда будет присвоено значение latest docker push username/imagename:tag
Набор инструкций для сборки образа.
# Базовый образ FROM ubuntu # Информация о создателе MAINTAINER Username <mail@mail.com> # Команды для выполнения внутри контейнера RUN apt update && apt install cowsay -y && ln -s /usr/games/cowsay /usr/bin/cowsay # Что выполнять по умолчанию при запуске контейнера ENTRYPOINT ["cowsay"]
# Собрать образ docker build -t username/imagename . # Запустить docker run username/imagename "test" # Если бы в Dockerfile не был указан параметр ENTRYPOINT, # то запускать надо было бы так: docker run username/imagename cowsay "test"
Например, запускается MariaDB:
docker run --name mysqlserver -e MYSQL_ROOT_PASSWORD=123456 -d mariadb
Затем нужно запустить adminer и связать его с MariaDB. Параметр --link делает в /etc/hosts контейнера запись db, указывающую на контейнер mysqlserver.
docker run --link mysqlserver:db -p 8080:8080 adminer
Более современный вариант связи - через publish services.
docker-compose.yaml = docker-compose.yml
# Use root/example as user/password credentials version: '3.1' services: db: image: mariadb # build: ./db Можно указать вместо image сборку из Dockerfile в каталоге db restart: always environment: MYSQL_ROOT_PASSWORD: example adminer: image: adminer restart: always ports: - 8080:8080
# Запустить docker-compose up # Запуск в фоне docker-compose up -d # Список контейнеров, запущенных через compose docker-compose ps # Собрать, пересобрать проект docker-compose build # Удалить контейнер с БД (по имени сервиса) docker-compose rm db
Если в Windows установлен Docker toolbox, то вход в браузере через localhost не сработает, нужно заходить на IP виртуальной машины, который можно узнать, выполнив команду
docker-machine ip default
Назначение путям внутри контейнера локальных каталогов на хосте. Удобно тем, что при остановке или уничтожении контейнера данные остаются в целости.
Есть 3 способа указания томов:
docker run -v /data ubuntu
Здесь каталог /data внутри контейнера станет томом, и всё, что пишется в /data, сохраняется на этот том. Местоположение тома можно посмотреть командой
docker inspect -f {{.Mounts}} containername
Обратное тоже верно - если что-то положить в каталог монтирования тома, это сразу будет видно в контейнере. Можно явно указать соответствие каталогов на хосте и внутри контейнера:
docker run -v /localdir:/data ubuntu
VOLUME /data
Сопоставление явно указанного локального каталога и каталога внутри контейнера не работает.
VOLUME ./localdir:/data
volumes: - ./localdir:/data - ./database:/var/lib/mysql
Необходимо 3 сервиса: PHP, MySQL и Composer.
Файл .env с переменными:
# Paths DB_PATH_HOST=./databases APP_PATH_HOST=./blog APP_PATH_CONTAINER=/var/www/html/
В данном случае в папку ./blog кладётся проект, например, клон репозитория Github.
docker-compose.yml:
version: '3' services: web: build: ./web environment: - APACHE_RUN_USER=www-data volumes: - ${APP_PATH_HOST}:${APP_PATH_CONTAINER} ports: - 8080:80 working_dir: ${APP_PATH_CONTAINER} db: image: mariadb restart: always environment: MYSQL_ROOT_PASSWORD: example volumes: - ${DB_PATH_HOST}:/var/lib/mysql adminer: image: adminer restart: always ports: # должны быть разными для хоста у разных сервисов - 6080:8080 composer: image: composer:1.6 volumes: - ${APP_PATH_HOST}:${APP_PATH_CONTAINER} working_dir: ${APP_PATH_CONTAINER} command: composer install
./web/Dockerfile
FROM php:7.2-apache RUN docker-php-ext-install pdo_mysql \ && a2enmod rewrite
# Сборка и запуск docker-compose up --build # Запустить bash в сервисе-контейнере web # Благодаря указанию working_dir, оболочка сразу попадёт в /var/www/html/ docker-compose exec web bash
В Windows при запуске контейнера с Volume ругается на то, что нет прав. Для этого нужно зайти в настройки Docker Desktop и на вкладке Shared Drives поставить галки на нужных дисках.
Ситуация может осложняться тем, что у пользователя нет пароля, и в таком случае расшарить диски не получится, так как Докер не даёт нажать ОК, если нет пароля пользователя.
Решение - завести нового локального пользователя с бессрочным паролем, дать ему права локального админа, расшарить диски под его именем, а затем дать ему полные права на локальный каталог с проектами.
Get started
Docker CLI
Top 10 Docker CLI commands you can’t live without
## List Docker CLI commands docker docker container --help ## Display Docker version and info docker --version docker version docker info ## Execute Docker image docker run hello-world ## List Docker images docker image ls ## List Docker containers (running, all, all in quiet mode) docker container ls docker container ls --all docker container ls -aq # Containers docker build -t friendlyhello . # Create image using this directory's Dockerfile docker run -p 4000:80 friendlyhello # Run "friendlyname" mapping port 4000 to 80 docker run -d -p 4000:80 friendlyhello # Same thing, but in detached mode docker container ls # List all running containers docker container ls -a # List all containers, even those not running docker container stop <hash> # Gracefully stop the specified container docker container kill <hash> # Force shutdown of the specified container docker container rm <hash> # Remove specified container from this machine docker container rm $(docker container ls -a -q) # Remove all containers docker image ls -a # List all images on this machine docker image rm <image id> # Remove specified image from this machine docker image rm $(docker image ls -a -q) # Remove all images from this machine docker login # Log in this CLI session using your Docker credentials docker tag <image> username/repository:tag # Tag <image> for upload to registry docker push username/repository:tag # Upload tagged image to registry docker run username/repository:tag # Run image from a registry # Services docker stack ls # List stacks or apps docker stack deploy -c <composefile> <appname> # Run the specified Compose file docker service ls # List running services associated with an app docker service ps <service> # List tasks associated with an app docker inspect <task or container> # Inspect task or container docker container ls -q # List container IDs docker stack rm <appname> # Tear down an application docker swarm leave --force # Take down a single node swarm from the manager # Swarms docker-machine create --driver virtualbox myvm1 # Create a VM (Mac, Win7, Linux) docker-machine create -d hyperv --hyperv-virtual-switch "myswitch" myvm1 # Win10 docker-machine env myvm1 # View basic information about your node docker-machine ssh myvm1 "docker node ls" # List the nodes in your swarm docker-machine ssh myvm1 "docker node inspect <node ID>" # Inspect a node docker-machine ssh myvm1 "docker swarm join-token -q worker" # View join token docker-machine ssh myvm1 # Open an SSH session with the VM; type "exit" to end docker node ls # View nodes in swarm (while logged on to manager) docker-machine ssh myvm2 "docker swarm leave" # Make the worker leave the swarm docker-machine ssh myvm1 "docker swarm leave -f" # Make master leave, kill swarm docker-machine ls # list VMs, asterisk shows which VM this shell is talking to docker-machine start myvm1 # Start a VM that is currently not running docker-machine env myvm1 # show environment variables and command for myvm1 eval $(docker-machine env myvm1) # Mac command to connect shell to myvm1 & "C:\Program Files\Docker\Docker\Resources\bin\docker-machine.exe" env myvm1 | Invoke-Expression # Windows command to connect shell to myvm1 docker stack deploy -c <file> <app> # Deploy an app; command shell must be set to talk to manager (myvm1), uses local Compose file docker-machine scp docker-compose.yml myvm1:~ # Copy file to node's home dir (only required if you use ssh to connect to manager and deploy the app) docker-machine ssh myvm1 "docker stack deploy -c <file> <app>" # Deploy an app using ssh (you must have first copied the Compose file to myvm1) eval $(docker-machine env -u) # Disconnect shell from VMs, use native docker docker-machine stop $(docker-machine ls -q) # Stop all running VMs docker-machine rm $(docker-machine ls -q) # Delete all VMs and their disk images
Докер упрощает установку и запуск программ без возни с зависимостями. Это платформа, создающая и запускающая т. н. контейнеры. Контейнер - это запущенный изолированный экземпляр приложения из заранее заготовленного образа (image).
При установке Докера ставится 2 компонента - клиент (CLI, управление) и сервер (функциональная часть). При установке Докера рекомендуется регистрация на docker.com. После установки Докера и перезагрузки нужно вбить рег. данные в запущенный Докер для доступа к Docker Cloud.
# Версия: docker version # Запустить тестовый контейнер: docker run hello-world
Если образа нет в локальном кэше (image cache), он скачивается автоматически из репозитория (Docker Hub).
Программы взаимодействуют с железом через ядро (kernel) путём системных запросов (system call). Контейнер - это изолированная область для запуска процессов, использует namespacing для изолирования системных ресурсов для запуска процесса, и control groups (cgroups) для лимита выделяемой мощности. В частности, это позволяет, например, запускать программы с разными зависимостями, в то время как эти зависимости не могли бы работать одновременно в одной и той же ОС. Т. е., виртуализация приложения или сервиса внутри одной ОС.
Так как namespacing и cgroups - сугубо линуксовое явление, то, когда Докер ставится на Windows или Mac, в реальности туда ставится виртуальная машина с линуксом, через которую и работает контейнеризация.
# Аргументы контейнеру docker run busybox echo hi there docker run busybox ls # Список запущенных контейнеров docker ps # Список когда-либо запущенных контейнеров docker ps --all
docker run = docker create + docker start
docker create hello-world # создаёт контейнер и показывает его ID docker start -a ID # запускает конкретный контейнер с выводом результатов docker start ID # просто запускает контейнер без вывода результатов на экран. Выводится только ID запущенного контейнера. docker logs ID # посмотреть вывод уже отработавшего контейнера по команде docker start ID # (не в реальном времени, а то, что успело выполниться на момент запроса).
Запущенный однажды контейнер лежит на диске и его всегда можно вызвать по ID, но команду заменить не получится. Например, запускали
docker run busybox echo hi there # Если запустить docker start -a ID echo bye there # то ничего не получится.
# Удаление остановленных контейнеров, используемых ими сетей, кэша # (образы нужно будет скачивать заново из Docker Hub) docker system prune
docker stop ID docker kill ID # Если stop не отрабатывает за 10 сек, автоматически посылается kill.
# Запуск команд внутри контейнера docker exec -it ID <command> # Например, docker exec -it ID redis-cli # Без ключа -it (-i -t) процесс запустится, но без возможности ввода. # -i - STDIN, -t - вывод текста. Можно запустить и docker exec -i ID redis-cli # но в этом случае не будет выводиться консоль и будет показываться # только ввод пользователя, выглядит непривычно и неудобно, хотя всё работает.
# Запустить "удалённую" консоль в контейнере docker exec -it ID sh # потом можно запускать redis-cli и всё остальное за один сеанс, чтобы не вводить каждый раз docker exec -i ID redis-cli # Создать контейнер и запустить там консоль docker run -it busybox sh
Файловые системы разных контейнеров изолированы, и созданное что-либо в одном контейнере не видится из других.
Для создания собственного образа нужно написать Dockerfile, который через клиент передаётся серверу, который на основе докерфайла создаёт образ.
Докерфайл пишется так:
Создаётся каталог, где размещается файл Dockerfile (с большой буквы, без расширения). Например:
FROM alpine RUN apk add --update redis CMD ["redis-server"]
Затем запускается
docker build .
На каждом этапе сборки создаётся промежуточный контейнер, где выполняется команда, затем контейнер стирается и выполняется следующая.
Если добавить команду на 2 этап, например,
RUN apk add --update gcc
то предыдущие команды уже не будут выполняться, потому что результат уже есть в кэше. Но если поменять порядок команд, то всё будет выполняться заново, т. к. в кэше нет таких контейнеров.
# Чтобы задать человеческое имя образу при сборке: docker build -t YourDockerID/RepoOrProjectName:Version . # Например, docker build -t username/redis:latest . # Версия latest грузится и запускается Докером по умолчанию. # Если просто написать docker build -t username/redis, # то версия latest добавится автоматически.
# Можно сделать образ руками, запустив все нужные команды в консоли, а затем "закоммитив" образ. docker run -it alpine sh apk add --update redis # Из другой консоли: docker ps # (узнать ID изменяемого контейнера) docker commit -c 'CMD ["redis-server"]' ID # Создастся образ с новым ID.
Чтобы запустить образ, можно не копировать весь ID, достаточно части, которая будет уникальной.
Задача: сделать образ, который при обращении браузером на порт 8080 показывает страничку. Функционал состоит из package.json
{ "dependencies": { "express": "*" }, "scripts": { "start": "node index.js" } }
и index.js
const express = require('express'); const app = express(); app.get('/', (req, res) => { res.send('How are you doing'); }); app.listen(8080, () => { console.log('Listening on port 8080'); });
Dockerfile:
# FROM alpine - не сработает, т. к. там нет npm, и на втором шаге будет ошибка FROM node:alpine # это вариант (тэг) alpine установки образа node, где npm есть # RUN npm install - не сработает, т. к. внутри контейнера нет файла package.json, # поэтому сначала надо их скопировать туда COPY ./ ./ # копирование в контексте сборочного каталога. В данном случае - скопировать все файлы RUN npm install # теперь можно запускать установку CMD ["npm", "start"]
# Собрать: docker build -t username/simpleweb . # Запуск: docker run username/simpleweb
Но localhost:8080 работать не будет, т. к. нужно настроить проброс порта в контейнер.
Для этого нужно запустить контейнер так:
docker run -p 8080:8080 username/simpleweb # запросы на localhost:порт в контейнере
COPY ./ ./ бросает файлы в корень файловой системы контейнера, что нехорошо. Для решения проблемы добавляется параметр WORKDIR /usr/app и все дальнейшие команды будут выполняться в контексте указанного каталога.
Есть нюанс: если изменить что-либо в коде index.js и запустить пересборку образа, то это потянет за собой npm install заново. Решение - так как для запуска npm install нужен только package.json, то Dockerfile нужно писать так:
COPY ./package.json ./ RUN npm install COPY ./ ./
Это предоствратит постоянную инсталляцию с нуля зависимостей npm при пересборке.
Задача: сделать веб-страницу со счётчиком посещений. Node отвечает за веб-часть, а Redis считает посещения. Можно засунуть Node и Redis в один контейнер, но при масштабировании (увеличении кол-ва контейнеров) получится так, что посещения будут записываться в разные экземпляры Redis. Поэтому нужно сделать два контейнера - один с Node, другой с Redis, и масштабировать только контейнеры с Node.
Так как два контейнера должны взаимодействовать между собой, нужно их соответственно настроить, т. к. изначально всё, что внутри контейнера, изолировано от внешней среды. Есть два способа - использовать Docker CLI (что очень неудобно) или docker-compose, который позволяет избежать постоянного ввода одних и тех же команд через Docker CLI и позволяет автоматизировать работу.
Docker-compose использует конфиг docker-compose.yml, куда добавляются команды Docker CLI.
version: '3' services: redis-server: image: 'redis' node-app: build . ports: - "4001:8081"
docker-compose автоматически открывает сетевой доступ между контейнерами, созданными в docker-compose.yml.
В качестве хоста в index.js нужно указывать название сервиса, указанного в docker-compose.yml, например,
host: 'redis-server', port: 6379
# Запуск: docker-compose up # = docker run myimage docker-compose up -d # (запуск в фоне) # Сборка и запуск docker-compose up --build # = docker build . + docker run myimage # Остановка (всех контейнеров) docker-compose down
Автоперезапуск контейнера при падении
Варианты перезапуска:
Добавляется в docker-compose.yml в параметры конкретного сервиса.
restart: always
# Статус контейнеров docker-compose docker-compose ps
показывает статус контейнеров, перечисленных в docker-compose.yml. Если этого файла в каталоге нет, то будет ошибка, т. к. docker-compose не знает, о чём идёт речь.
Этапы развития ПО - разработка, тестирование, продуктивное использование.
Создаётся 2 файла: Dockerfile.dev (для разработки) и просто Dockerfile (продакшн).
Вызвать Dockerfile.dev можно так:
docker build -f Dockerfile.dev .
Volume - ссылки изнутри контейнера на ресурсы вовне, например, на файлы и каталоги, чтобы не пересобирать и не перезапускать контейнер каждый раз, как требуются изменения. Например,
docker run -p 3000:3000 -v /app/node_modules -v $(pwd):/app <image_id>
$(pwd):/app - $(pwd) - present working directory в хостовой системе, привязать её к каталогу /app внутри контейнера.
-v /app/node_modules указывает, что нужно использовать этот каталог внутри контейнера и не использовать ссылку вовне (синтаксис без :). Это используется, если ресурсы имеются в контейнере и их нет на хосте, вроде исключения.
Чтобы избежать излишней сложности запуска контейнеров через docker run, можно использовать docker-compose. В docker-compose.yml нужно указать:
version: '3' services: web: build: context: . dockerfile: Dockerfile.dev ports: - "3000:3000" volumes: - /app/node_modules - .:/app
и запустить docker-compose up. Секция build содержит опции context: и dockerfile:, т. к. используется нестандартное имя докерфайла Dockerfile-dev.
После того, как создан Volume, который смотрит из контейнера на хост, возникает вопрос, нужна ли операция копирования (COPY . .) в докерфайле? Лучше её оставить, т. к. в будущем может быть принято решение не использовать docker-compose, или этот докерфайл будет использован впоследствии для создания докерфайла для продуктивного использования.
Тестирование: после
docker build -f Dockerfile.dev . # запустить docker run -it ID npm run test
Тесты в данном случае находятся в src/app.test.js, и чтобы менять их на ходу, можно создать для них Volume в docker-compose.yml, но есть другой способ - выполнить команду для запущенного контейнера:
docker exec -it ID npm run test
Можно применять это как временное решение, т. к. надо выяснять ID запущенного контейнера.
Есть ещё один способ - добавить сервис для тестирования в docker-compose.yml
services: tests: build: context: . dockerfile: Dockerfile.dev volumes: - /app/node_modules - .:/app command: ["npm", "run", "test"]
# Далее нужно пересобрать и запустить контейнеры docker-compose up --build
После этого можно менять файл тестов и видеть, как они сразу выполняются в терминале. Недостаток в том, что нельзя управлять выполнением тестов, т. к. нет меню управления.
# Вывод процесса в контейнере на стандартный вывод, т. е. экран, не помогает. docker attach -it ID
Это связано с тем, что процесс запускается отдельным потоком, а подключение через attach происходит в первичную консоль. Можно посмотреть процессы, запустив
docker exec -it ID sh # и там выполнив команду ps
Замена движка на nginx. Вопрос в том, что в данном случае нужно использовать ДВА исходных образа - node и nginx, который нужно добавить после сборки npm. Dockerfile:
#Build phase FROM node:alpine as builder WORKDIR '/app' COPY package.json . RUN npm install COPY . . RUN npm run build # всё барахло (результат сборки) теперь находится в /app/build # Run phase FROM nginx # копировать из builder (пред. стадии), # из каталога с барахлом в стандартный каталог nginx COPY --from=builder /app/build /usr/share/nginx/html
# Собрать образ для продакшена: docker build . # Запустить: docker run -p 8080:80 ID
Теперь с задействованием Гитхаба - двух веток feature и master. Master делает pull request из feature. Код проверяется Travis CI, который при новых коммитах также делает pull request кода и запускает проверки. Сначала нужно создать открытый пустой репозиторий на Гитхабе. Затем
git init git add . git commit -m "initial commit"
Содержимое каталога скопируется.
git remote add origin <github repo link>
travis-ci.org, авторизовать Тревис на Гитхабе, указать нужный репозиторий.
Настройки тревиса буду указаны в файле .travis.yml, его нужно положить в корень каталога проекта. Схема файла - копировать запущенный докер, построить образ из Dockerfile.dev, настройки тестов, настройки выкладки кода на AWS (Amazon Web Services) и т. п.
sudo: required services: - docker before_install: - docker build -t username/docker-react (or testme or smth else) -f Dockerfile.dev . script: - docker run username/docker-react npm run test -- --coverage
– –coverage нужен, чтобы после прохождения тестов скрипт выходил, а не ждал выбора пользователя. Затем выполняем
git add . git commit -m "added travis file" git push origin master
После этого коммит проваливается в Гит и забирается Тревисом, который действует сообразно .travis.yml и выдаёт результат теста.
Далее идём в AWS и ищем там Elastic beanstalk, создать приложение, например, docker-react, создать там окружение - web server environment, платформа - docker.
Затем нужно добавить в .travis.yml
deploy: provider: elasticbeanstalk region: "us-west-2" (берётся из URL приложения на Амазоне) app: "docker-react" env: "Docker-env" bucket_name: "elasticbeanstalk-us-west-2-28354762534" bucket_path: "docker" (= app:) on: branch: master
Затем на Амазоне надо добавить IAM - ключи для доступа и права для пользователя, например, docker-react-travis-ci. Ключи надо добавить в Environment variables Тревиса, но не в .travis.yml! В .travis.yml в раздел deploy затем нужно добавить
access_key_id: $AWS_ACCESS_KEY secret_access_key: secure: "$AWS_SECRET_KEY"
git add . git commit -m "added travis deploy config" git push origin master
Чтобы пробросить порт из контейнера в случае использования elastic-beanstalk, нужно прописать в Dockerfile после FROM nginx:
EXPOSE 80
Потом опять та же мантра:
git add . git commit -m "added expose 80" git push origin master
Сервис состоит из нескольких компонентов - самого приложения (подпапки client, api и worker), Postgres и Redis. Dockerfile подпапок client, api и worker:
FROM node:alpine WORKDIR '/app' COPY ./package.json ./ RUN npm install COPY . . CMD ["npm", "run", "start"]
Docker-compose.yml в корне:
version: '3' services: # просто добавляем соответствующие контейнеры последних версий postgres: image: 'postgres:latest' redis: image: 'redis:latest' api: build: dockerfile: Dockerfile # без путей context: ./api # путь здесь volumes: - /app/node_modules # не трогать эту папку внутри контейнера - ./api:/app # скопировать в /app контейнера содержимое ./server # Следующая секция - переменные (configuration variables). # Переменные задают параметры контейнера при его запуске (не при создании образа). # В примере параметры хранятся в файле ./server/keys.js, # там всякие настройки базы, адреса хостов и порты, пароли и т. п. # Есть два способа задать значение переменной: # variableName=value - жёстко задать значение # variableName - взять значение с хоста environment: - REDIS_HOST=redis # в данном случае, имя сервиса - REDIS_PORT=6379 # и т. д. client: build: dockerfile: Dockerfile context: ./client volumes: - /app/node_modules - ./client:/app worker: build: dockerfile: Dockerfile context: ./worker volumes: - /app/node_modules - ./worker:/app
Далее нужно добавить nginx как редиректор разных URL в разные сервисы. Это нужно, чтобы не плодить порты доступа в контейнеры этих сервисов. На примере nginx - настройки в файле default.conf:
Нужно создать в проекте папку nginx, а в ней - default.conf:
upstream client { server client:3000; } upstream api { server api:5000; } server { listen 80; location / { proxy_pass http://client; } # Починить ругань на websockets, # которые обращаются на /sockjs-node location /sockjs-node { proxy_pass http://client; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; } location /api { rewrite /api/(.*) /$1 break; proxy_pass http://api; } }
Дальше нужно создать Dockerfile в папке ./nginx:
FROM nginx WORKDIR '/app' COPY ./default.conf /etc/nginx/conf.d/default.conf
Добавить в Docker-compose.yml сервис nginx:
nginx: restart: always build: dockerfile: Dockerfile context: ./nginx ports: - 3050:80