单机编排&&私有镜像仓库
9156 字
46 分钟
单机编排&&私有镜像仓库

单机编排&&私有镜像仓库
[TOC]
docker-compose(单体服务)
docker-compose是Docker公司用来编排docker容器的一款工具允许用户通过一个 YAML 文件 --> 来配置应用程序的"服务"、"网络"和"数据卷"等# 通常称为 docker-compose.yml==================================# 自行清理环境[root@Docker ~]# docker rm -f $(docker ps -aq)[root@Docker ~]# docker network prune -f[root@Docker ~]# docker volume prune -fa
1)检测是否安装[root@Docker ~]# rpm -qa | grep docker-composedocker-compose-plugin-5.1.3-1.el10.x86_64[root@Docker ~]# docker compose versionDocker Compose version v5.1.3
2)单服务服务编排[root@Docker ~]# rm -rf ./*[root@Docker ~]# mkdir ./docker-compose && cd ./docker-compose# 创建项目名 --> docker-compose[root@Docker docker-compose]# vim myweb.yml'名字随意,可以 -f 指定运行,但必须是yml结尾⚠️'services:# 定义一个服务# 有s myweb: # 服务的名称 image: nginx:latest # 指定镜像的名称 container_name: c1 # 指定容器的名称 ports: # 指定该端口映射 - "81:80"
3)启动服务 --> up[root@Docker docker-compose]# docker compose -f myweb.yml up -d✅️ -f 指定compose文件✅️ -d 表示放在后台运行[+] up 2/2 ✔ Network docker-compose_default Created # 创建网络 ✔ Container c1 Started # 启动容器[root@Docker docker-compose]# docker network ls | grep defaultdocker-compose_default bridge local📌 先创建了个网络[root@Docker docker-compose]# docker psnginx:latest "/docker-entrypoint.…" 3 minutes ago Up 3 minutes 0.0.0.0:81->80/tcp, [::]:81->80/tcp c1📌 又跑了一个Nginx容器
4)查看运行状态[root@Docker docker-compose]# docker-compose psdocker-compose: command not found[root@Docker docker-compose]# docker compose psno configuration file provided: not found"没有找到配置文件"# 因为我们改了默认的名字,所以得-f指定才能够找到![root@Docker docker-compose]# docker compose -f myweb.yml ps✅️ ps --> 这个项目运行的"容器状态"# 这个项目只跑了一个容器c1 nginx:latest "/docker-entrypoint.…" myweb 9 minutes ago Up 9 minutes 0.0.0.0:81->80/tcp, [::]:81->80tcp[root@Docker docker-compose]# curl localhost:81<title>Welcome to nginx!</title>[root@Docker docker-compose]# docker compose ls✅️ 列出正在运行的"compose项目"NAME STATUS CONFIG FILESdocker-compose running(1) /root/docker-compose/myweb.yml👆项目目录 👆正在运行的容器 👆主配置文件[root@Docker docker-compose]# docker inspect c1 | grep IPAddress "IPAddress": "172.22.0.2",
5)停止并"移除"容器 --> down[root@Docker docker-compose]# docker compose -f myweb.yml down -t 0✅️ -t 0 立即执行[+] down 2/2 ✔ Container c1 Removed ✔ Network docker-compose_default Removed[root@Docker docker-compose]# docker network ls | grep default# 刚才自动创建的网络被移除了 ✅️[root@Docker docker-compose]# docker ps -a | grep c1# 自动创建的容器也被移除了 ✅️'mysql单体服务'# 自行清理环境
1)环境准备[root@Docker ~]# pwd/root[root@Docker ~]# rm -rf ./*[root@Docker ~]# mkdir ./mydb && cd ./mydb[root@Docker mydb]# docker images | grep mysqlmysql:8.0.36 a53272402242 833MB
2)编写主配置文件[root@Docker mydb]# vim db_server.ymlservices: db_server: image: mysql:8.0.36 container_name: db01 ports: - "3306:3306" environment: # 定义环境变量 # 相当于 docker run -e key=value MYSQL_ALLOW_EMPTY_PASSWORD: "yes" MYSQL_DATABASE: "wordpress" MYSQL_USER: "jiuzhao" MYSQL_PASSWORD: "passwd" command: ["--character-set-server=utf8", "--collation-server=utf8_bin", "--default-authentication-plugin=mysql_native_password"] # 向容器传递参数 # 覆盖原来的CMD指令 --> mysqld# 既然我们学完Dockerfile了# 我们来看一下镜像的详细信息[root@Docker ~]# docker inspect mysql:8.0.36"Config": { "ExposedPorts": { <-- 暴漏端口 ✅️ "3306/tcp": {}, "33060/tcp": {} }, "Env": [ <-- 环境变量 ✅️ "GOSU_VERSION=1.17", "MYSQL_MAJOR=8.0", "MYSQL_VERSION=8.0.36-1.el8", "MYSQL_SHELL_VERSION=8.0.36-1.el8" ], "Entrypoint": [ <-- 固定入口 ✅️ "docker-entrypoint.sh" ], # 这是一个初始化脚本 # 👇的CMD用来传递参数 "Cmd": [ "mysqld" <-- 默认参数 ✅️ ], # 默认执行 docker-entrypoint.sh mysqld --character-set-server=utf8 --collation-server=utf8_bin # 会覆盖掉原来的CMD指令 "Volumes": { <-- 数据卷 ✅️ "/var/lib/mysql": {} }, # 默认创建匿名数据卷3)启动服务[root@Docker mydb]# docker compose -f db_server.yml up -d# 同样是放在后台运行[+] up 2/2 ✔ Network mydb_default Created --> ✅️ 网络 ✔ Container db01 Started --> ✅️ 容器[root@Docker mydb]# docker compose lsNAME STATUS CONFIG FILESmydb running(1) /root/mydb/db_server.yml[root@Docker mydb]# docker network ls | grep mydba425873c55f1 mydb_default bridge local[root@Docker mydb]# docker compose -f db_server.yml psdb01 mysql:8.0.36 "docker-entrypoint.s…" db_server 46 seconds ago Up 45 seconds 0.0.0.0:3306->3306/tcp, [::]:3306->3306/tcp, 33060/tcp
4)测试验证[root@Docker mydb]# docker exec -it db01 mysqlWelcome to the MySQL monitor.mysql> show databases;+--------------------+| Database |+--------------------+| information_schema || mysql || performance_schema || sys || wordpress ✅️ |+--------------------+
5)停止&&移除mysql> exitBye[root@Docker mydb]# docker compose -f db_server.yml down -t 0[+] down 2/2 ✔ Container db01 Removed ✔ Network mydb_default Removed数据持久化
1)准备工作[root@Docker ~]# rm -rf ./*[root@Docker ~]# mkdir ./db-volume && cd ./db-volume
2)编写配置文件[root@Docker db-volume]# vim docker-compose.ymlservices: db_server: image: mysql:8.0.36 container_name: db02 ports: - "3306:3306" environment: MYSQL_ALLOW_EMPTY_PASSWORD: "yes" MYSQL_DATABASE: "wordpress" MYSQL_USER: "jiuzhao" MYSQL_PASSWORD: "passwd" command: ["--character-set-server=utf8", "--collation-server=utf8_bin", "--default-authentication-plugin=mysql_native_password"] volumes: # 存储卷名称(别名):容器挂载点:读写模式(rw,ro) - db:/var/lib/mysql:rw - web:/code:rw# db和 web是内部引用名,方便在 compose 文件中使用# 先引用,后声明
volumes: db: name: mysql-volume # 自定义 Docker 中实际的卷名称 web: name: web-server# mysql-volume和web-server是 Docker 实际创建的真实卷名==================================================# ⚠️如果不指定 name: web-server# ⚠️Docker 会自动创建 volumes: <-- "服务引用的数据卷" - db:/var/lib/mysql:rw - web:/code:rw
volumes:<-- "声明数据卷" db: name: mysql-volume web: "web没有指定name"[root@Docker db-volume]# docker compose up -d[+] up 4/4 ✔ Network xxx 👇 项目名+别名 ✔ Volume db-volume_web⚠️ <-- 自动创建的名称 📌 ✔ Volume mysql-volume <-- 手动指定的名称 ✅️ ✔ Container xxx'都写到这了,不手动指定一下name吗???'================================================== volumes: - db:/var/lib/mysql:rw - web:/code:rw'只有引用卷db和web ,没有声明'[root@Docker db-volume]# docker compose up -d❌️ undefined volume db: invalid compose project=================================================='最后一种匿名数据卷' 🤡 不推荐 volumes: # 里面只有容器里面的路径 # 不能指定模式(rw/ro) - /var/lib/mysql - /code[root@Docker db-volume]# docker volume prune -f[root@Docker db-volume]# docker compose up -d[root@Docker db-volume]# docker volume lsDRIVER VOLUME NAMElocal 192fdxxx...local 5037bxxx...'后面是一串神秘的字符'Note
这样分开定义是为了==提供灵活性==
既可以在 compose 内部使用简短的别名,又可以精确控制实际的卷名称
3)启动服务[root@Docker db-volume]# docker compose up -d[+] up 4/4 ✔ Network db-volume_default Created ✔ Volume web-server Created ✔ Volume mysql-volume Created ✔ Container db02 Started"两个数据卷都创建成功了"[root@Docker db-volume]# docker volume lslocal mysql-volumelocal web-server[root@Docker db-volume]# docker compose psdb02 mysql:8.0.36 "docker-entrypoint.s…" db_server 58 seconds ago Up 57 seconds 0.0.0.0:3306->3306/tcp, [::]:3306->3306/tcp, 33060/tcp[root@Docker db-volume]# docker compose lsNAME STATUS CONFIG FILESdb-volume running(1) /root/db-volume/docker-compose.yml
4)停止容器&&移除网络[root@Docker db-volume]# docker compose down[+] down 2/2 ✔ Container db02 Removed ✔ Network db-volume_default Removed'创建的时候是4个,移除的时候只移除了两个'✅️ 数据卷没有被移除![root@Docker db-volume]# docker volume lslocal mysql-volumelocal web-server
5) -v# 移除服务时,同时移除数据卷[root@Docker db-volume]# docker compose down -v[+] down 2/2 ✔ Volume web-server Removed ✔ Volume mysql-volume Removed# 重新执行了一遍,把数据卷也移除了!
6)综合演示[root@Docker db-volume]# docker compose up -d[+] up 4/4 ✔ Network db-volume_default Created ✔ Volume mysql-volume Created ✔ Volume web-server Created ✔ Container db02 Started[root@Docker db-volume]# docker compose down -v -t 0[+] down 4/4 ✔ Container db02 Removed ✔ Volume mysql-volume Removed ✔ Volume web-server Removed ✔ Network db-volume_default Removed"工作中,用的最多的就是 up down"自定义网络
1)准备[root@Docker ~]# rm -rf ./*[root@Docker ~]# mkdir db-network && cd db-network[root@Docker db-network]# docker network lsNETWORK ID NAME DRIVER SCOPE66cc72cc26cd bridge bridge localbf4d048e4eed host host local5879c499792c none null local'现在只有3个默认的网络'
2)配置文件[root@Docker db-network]# vim docker-compose.ymlservices: db_server: image: mysql:8.0.36 container_name: db02 ports: - "3306:3306" environment: MYSQL_ALLOW_EMPTY_PASSWORD: "yes" MYSQL_DATABASE: "wordpress" MYSQL_USER: "jiuzhao" MYSQL_PASSWORD: "passwd" command: ["--character-set-server=utf8", "--collation-server=utf8_bin", "--default-authentication-plugin=mysql_native_password"] networks: # 只引用网络,没有声明(定义)网络 # 下面的这些只是别名 - db_net - web_net[root@Docker db-network]# docker compose up -d❌️ undefined network web_net: invalid compose project# 在项目中并没有被定义(声明)'自然是起不来的'================================= networks: - db_net - web_net# 先引用网络(别名),后声明
networks:# 声明真实的网络# name前面没有 - db_net: name: my_db web_net: name: my_web backup: name: my_backup[root@Docker db-network]# docker compose up -d'上面虽然声明了3个网络,但只创建了2个'# 只有db_net和web_net被服务引用,所以才会被创建[+] up 3/3 ✔ Network my_web Created ✅️ ✔ Network my_db Created ✅️ ✔ Container db02 Started[root@Docker db-network]# docker network lsNETWORK ID NAME DRIVER SCOPEdc02802cf1a2 my_db bridge local88fee2060426 my_web bridge local[root@Docker db-network]# docker network inspect my_db | grep -A 4 "Config\""" "Config": [ { "Subnet": "172.19.0.0/16", "Gateway": "172.19.0.1" }# 有这两个配置足以✅️ '容器的网络从.2开始分配IP地址'[root@Docker db-network]# docker network inspect my_web | grep -A 4 "Config\""" "Config": [ { "Subnet": "172.18.0.0/16", "Gateway": "172.18.0.1" }[root@Docker db-network]# docker inspect db02 | grep IPAddress "IPAddress": "172.19.0.2", "IPAddress": "172.18.0.2",# 对于我们刚创建的容器db02来说# 它是有两个网络的,都是从.2开始分配的IP地址
3)停止服务[root@Docker db-network]# docker compose down -t 0[+] down 3/3 ✔ Container db02 Removed ✔ Network my_web Removed ✔ Network my_db Removed'在停止服务的同时把网络也移除了!'✅️ 这里只有数据卷特殊,需要加-v才能移除 📌
4)声明网络的同时,指定驱动、子网、网关'这种是最麻烦的,不推荐' 🤡# 个人感觉,自定义网络名字就可以了[root@Docker db-network]# vim docker-compose.yml...xxxx # 省略 networks: - db_net - web_net
networks: db_net: name: my_db driver: bridge # 网络驱动,网桥 ipam: driver: default # 指定IPAM的驱动,一般使用默认值 config: # 只有subnet前面有 - - subnet: 172.28.0.0/16 gateway: 172.28.0.1 web_net: # 它使用默认的 name: my_web driver: bridge--------------------------------"IPAM": { "Driver": "default", "Options": null, "Config": [ { "Subnet": "172.28.0.0/16", "Gateway": "172.28.0.1" }[root@Docker db-network]# docker compose up -d[+] up 3/3 ✔ Network my_web Created ✔ Network my_db Created ✔ Container db02 Started'我们主要去看具体的网络配置'[root@Docker db-network]# docker network inspect my_db | grep -A 4 "Config\""" "Config": [ { "Subnet": "172.28.0.0/16", "Gateway": "172.28.0.1" } ✅️ 这个就是我们手动指定的网络[root@Docker db-network]# docker network inspect my_web | grep -A 4 "Config\""" "Config": [ { "Subnet": "172.18.0.0/16", "Gateway": "172.18.0.1" } 📌 自动创建的子网配置 # 它是接着docker 0 --> 172.17.0.1创建[root@Docker db-network]# docker compose down -t 0docker-compose(多服务编排)
wordpress部署
1)环境准备[root@Docker ~]# docker volume prune -fa[root@Docker ~]# docker network prune -f[root@Docker ~]# rm -rf ./*[root@Docker ~]# mkdir mul-wordpress && cd mul-wordpress
2)编写配置[root@Docker mul-wordpress]# vim docker-compose.ymlservices: db_server: image: mysql:8.0.36 container_name: db ports: - "3306:3306" environment: MYSQL_ALLOW_EMPTY_PASSWORD: "yes" MYSQL_DATABASE: "wordpress" MYSQL_USER: "jiuzhao" MYSQL_PASSWORD: "passwd" command: ["--character-set-server=utf8", "--collation-server=utf8_bin", "--default-authentication-plugin=mysql_native_password"] volumes: - db:/var/lib/mysql:rw networks: - wp-net # 指定容器的重启策略 restart: unless-stopped
wordpress: # 依赖的服务要优先启动 depends_on: - db_server image: wordpress:6.7.1 container_name: wp restart: unless-stopped ports: - "81:80" environment: WORDPRESS_DB_HOST: db_server:3306 # 这里目标主机填的是服务名,而并非容器名 WORDPRESS_DB_USER: jiuzhao WORDPRESS_DB_PASSWORD: passwd WORDPRESS_DB_NAME: wordpress volumes: - wp:/var/www/html networks: - wp-net
networks:# 两个服务使用的是一个网络 wp-net: name: wordpress-net driver: bridge ipam: driver: default config: - subnet: 172.18.0.0/16 gateway: 172.18.0.1
volumes:# 两个服务分别使用一个数据卷 db: name: mysql-volume wp: name: wordpress-volume3)启动服务[root@Docker mul-wordpress]# docker compose up -d[+] up 5/5 ✔ Network wordpress-net Created ✔ Volume mysql-volume Created ✔ Volume wordpress-volume Created ✔ Container db Started ✔ Container wp Started"一个网络,两个数据卷"[root@Docker mul-wordpress]# docker compose lsNAME STATUS CONFIG FILESmul-wordpress running(2) ~/mul-wordpress/docker-compose.yml[root@Docker mul-wordpress]# docker compose psdb mysql:8.0.36 "docker-entrypoint.s…" db_server About a minute ago Up About a minute 0.0.0.0:3306->3306/tcp, [::]:3306->3306/tcp, 33060/tcp-----------------------------------------------------wp wordpress:6.7.1 "docker-entrypoint.s…" wordpress About a minute ago Up About a minute 0.0.0.0:81->80/tcp, [::]:81->80/tcp[root@Docker mul-wordpress]# docker network lsefece4192527 wordpress-net bridge local[root@Docker mul-wordpress]# docker volume lsDRIVER VOLUME NAMElocal mysql-volumelocal wordpress-volume
4)访问测试http://10.0.0.99:81/"停止"docker compose down -t 0 -v
ES和kibana环境及env变量文件使用

Tip
-
官方链接:Docker-compose部署方案
-
GitHub链接: .env环境变量 和 docker-compose.yml

'三节点ES集群 + Kibana'1)确认镜像[root@Docker ~]# docker images | grep -E "elasticsearch|kibana"elasticsearch:9.4.4 5b4517639f5ckibana:9.4.4 a52f906140d4✅️ 9.4.4 已在本地
2)创建项目目录[root@Docker ~]# mkdir /root/multiple-es-cluster && cd /root/multiple-es-cluster
3)编写 .env 环境变量文件[root@Docker multiple-es-cluster]# vim .env# Password for the 'elastic' user (at least 6 characters)ELASTIC_PASSWORD=passwd# Password for the 'kibana_system' user (at least 6 characters)KIBANA_PASSWORD=passwd# Version of Elastic productsSTACK_VERSION=9.4.4# Set the cluster nameCLUSTER_NAME=My-ES# Set to 'basic' or 'trial' to automatically start the 30-day trialLICENSE=basic# Port to expose Elasticsearch HTTP API to the hostES_PORT=127.0.0.1:19200# Port to expose Kibana to the hostKIBANA_PORT=15601# Increase or decrease based on the available host memory (in bytes)MEM_LIMIT=1073741824KIBANA_MEM_LIMIT=2147483648# MEM_LIMIT 是 ES 三节点各 1G (1073741824),三节点集群内存压力大# KIBANA_MEM_LIMIT 是 kibana 单独 2G (2147483648)⭐ Kibana 9.x 内存吃紧 # 单独给 kibana 2G,堆升到 1.5G,稳定运行✅️ 环境变量文件: 把密码、版本、端口全部抽到 .env 里⭐ compose 里用 ${变量名} 引用,一处修改处处生效
4)编写 docker-compose.yml (官方模板)# 镜像引用为本地短名 elasticsearch:${STACK_VERSION} 和 kibana:${STACK_VERSION}# kibana 服务的 mem_limit 改为 ${KIBANA_MEM_LIMIT},ES 三节点保持 ${MEM_LIMIT}[root@Docker multiple-es-cluster]# vim docker-compose.ymlversion: "2.2"services: setup: image: elasticsearch:${STACK_VERSION} volumes: - certs:/usr/share/elasticsearch/config/certs user: "0" command: > bash -c ' if [ x${ELASTIC_PASSWORD} == x ]; then echo "Set the ELASTIC_PASSWORD environment variable in the .env file"; exit 1; elif [ x${KIBANA_PASSWORD} == x ]; then echo "Set the KIBANA_PASSWORD environment variable in the .env file"; exit 1; fi; if [ ! -f config/certs/ca.zip ]; then echo "Creating CA"; bin/elasticsearch-certutil ca --silent --pem -out config/certs/ca.zip; unzip config/certs/ca.zip -d config/certs; fi; if [ ! -f config/certs/certs.zip ]; then echo "Creating certs"; echo -ne \ "instances:\n"\ " - name: es01\n"\ " dns:\n"\ " - es01\n"\ " - localhost\n"\ " ip:\n"\ " - 127.0.0.1\n"\ " - name: es02\n"\ " dns:\n"\ " - es02\n"\ " - localhost\n"\ " ip:\n"\ " - 127.0.0.1\n"\ " - name: es03\n"\ " dns:\n"\ " - es03\n"\ " - localhost\n"\ " ip:\n"\ " - 127.0.0.1\n"\ > config/certs/instances.yml; bin/elasticsearch-certutil cert --silent --pem -out config/certs/certs.zip --in config/certs/instances.yml --ca-cert config/certs/ca/ca.crt --ca-key config/certs/ca/ca.key; unzip config/certs/certs.zip -d config/certs; fi; echo "Setting file permissions" chown -R root:root config/certs; find . -type d -exec chmod 750 \{\} \;; find . -type f -exec chmod 640 \{\} \;; echo "Waiting for Elasticsearch availability"; until curl -s --cacert config/certs/ca/ca.crt https://es01:9200 | grep -q "missing authentication credentials"; do sleep 30; done; echo "Setting kibana_system password"; until curl -s -X POST --cacert config/certs/ca/ca.crt -u "elastic:${ELASTIC_PASSWORD}" -H "Content-Type: application/json" https://es01:9200/_security/user/kibana_system/_password -d "{\"password\":\"${KIBANA_PASSWORD}\"}" | grep -q "^{}"; do sleep 10; done; echo "All done!"; ' healthcheck: test: ["CMD-SHELL", "[ -f config/certs/es01/es01.crt ]"] interval: 1s timeout: 5s retries: 120
es01: depends_on: setup: condition: service_healthy image: elasticsearch:${STACK_VERSION} volumes: - certs:/usr/share/elasticsearch/config/certs - esdata01:/usr/share/elasticsearch/data ports: - ${ES_PORT}:9200 environment: - node.name=es01 - cluster.name=${CLUSTER_NAME} - cluster.initial_master_nodes=es01,es02,es03 - discovery.seed_hosts=es02,es03 - ELASTIC_PASSWORD=${ELASTIC_PASSWORD} - bootstrap.memory_lock=true - xpack.security.enabled=true - xpack.security.http.ssl.enabled=true - xpack.security.http.ssl.key=certs/es01/es01.key - xpack.security.http.ssl.certificate=certs/es01/es01.crt - xpack.security.http.ssl.certificate_authorities=certs/ca/ca.crt - xpack.security.transport.ssl.enabled=true - xpack.security.transport.ssl.key=certs/es01/es01.key - xpack.security.transport.ssl.certificate=certs/es01/es01.crt - xpack.security.transport.ssl.certificate_authorities=certs/ca/ca.crt - xpack.security.transport.ssl.verification_mode=certificate - xpack.license.self_generated.type=${LICENSE} - xpack.ml.use_auto_machine_memory_percent=true mem_limit: ${MEM_LIMIT} ulimits: memlock: soft: -1 hard: -1 healthcheck: test: ["CMD-SHELL", "curl -s --cacert config/certs/ca/ca.crt https://localhost:9200 | grep -q 'missing authentication credentials'"] interval: 10s timeout: 10s retries: 120
es02: depends_on: - es01 image: elasticsearch:${STACK_VERSION} volumes: - certs:/usr/share/elasticsearch/config/certs - esdata02:/usr/share/elasticsearch/data environment: - node.name=es02 - cluster.name=${CLUSTER_NAME} - cluster.initial_master_nodes=es01,es02,es03 - discovery.seed_hosts=es01,es03 - ELASTIC_PASSWORD=${ELASTIC_PASSWORD} - bootstrap.memory_lock=true - xpack.security.enabled=true - xpack.security.http.ssl.enabled=true - xpack.security.http.ssl.key=certs/es02/es02.key - xpack.security.http.ssl.certificate=certs/es02/es02.crt - xpack.security.http.ssl.certificate_authorities=certs/ca/ca.crt - xpack.security.transport.ssl.enabled=true - xpack.security.transport.ssl.key=certs/es02/es02.key - xpack.security.transport.ssl.certificate=certs/es02/es02.crt - xpack.security.transport.ssl.certificate_authorities=certs/ca/ca.crt - xpack.security.transport.ssl.verification_mode=certificate - xpack.license.self_generated.type=${LICENSE} - xpack.ml.use_auto_machine_memory_percent=true mem_limit: ${MEM_LIMIT} ulimits: memlock: soft: -1 hard: -1 healthcheck: test: ["CMD-SHELL", "curl -s --cacert config/certs/ca/ca.crt https://localhost:9200 | grep -q 'missing authentication credentials'"] interval: 10s timeout: 10s retries: 120
es03: depends_on: - es02 image: elasticsearch:${STACK_VERSION} volumes: - certs:/usr/share/elasticsearch/config/certs - esdata03:/usr/share/elasticsearch/data environment: - node.name=es03 - cluster.name=${CLUSTER_NAME} - cluster.initial_master_nodes=es01,es02,es03 - discovery.seed_hosts=es01,es02 - ELASTIC_PASSWORD=${ELASTIC_PASSWORD} - bootstrap.memory_lock=true - xpack.security.enabled=true - xpack.security.http.ssl.enabled=true - xpack.security.http.ssl.key=certs/es03/es03.key - xpack.security.http.ssl.certificate=certs/es03/es03.crt - xpack.security.http.ssl.certificate_authorities=certs/ca/ca.crt - xpack.security.transport.ssl.enabled=true - xpack.security.transport.ssl.key=certs/es03/es03.key - xpack.security.transport.ssl.certificate=certs/es03/es03.crt - xpack.security.transport.ssl.certificate_authorities=certs/ca/ca.crt - xpack.security.transport.ssl.verification_mode=certificate - xpack.license.self_generated.type=${LICENSE} - xpack.ml.use_auto_machine_memory_percent=true mem_limit: ${MEM_LIMIT} ulimits: memlock: soft: -1 hard: -1 healthcheck: test: ["CMD-SHELL", "curl -s --cacert config/certs/ca/ca.crt https://localhost:9200 | grep -q 'missing authentication credentials'"] interval: 10s timeout: 10s retries: 120
kibana: depends_on: es01: condition: service_healthy es02: condition: service_healthy es03: condition: service_healthy image: kibana:${STACK_VERSION} volumes: - certs:/usr/share/kibana/config/certs - kibanadata:/usr/share/kibana/data ports: - ${KIBANA_PORT}:5601 environment: - SERVERNAME=kibana - ELASTICSEARCH_HOSTS=https://es01:9200 - ELASTICSEARCH_USERNAME=kibana_system - ELASTICSEARCH_PASSWORD=${KIBANA_PASSWORD} - ELASTICSEARCH_SSL_CERTIFICATEAUTHORITIES=config/certs/ca/ca.crt mem_limit: ${KIBANA_MEM_LIMIT} healthcheck: test: ["CMD-SHELL", "curl -s -I http://localhost:5601 | grep -q 'HTTP/1.1 302 Found'"] interval: 10s timeout: 10s retries: 120
volumes: certs: driver: local esdata01: driver: local esdata02: driver: local esdata03: driver: local kibanadata: driver: local========================================cluster.initial_master_nodes: # 首次选举 master 的候选节点discovery.seed_hosts: # 各节点去发现彼此的种子地址⭐ es01 只写 es02,es03; es02 写 es01,es03; es03 写 es01,es02 # 相互发现,组成一个集群xpack.security.http.ssl: # 对应 9200 端口,对外走 HTTPS(客户端/kibana/curl连接)xpack.security.transport.ssl: # 对应 9300 端口,对内走 TLS(节点间内部通信)⭐ 所以只映射 19200:9200 而不映射 9300,9300 仅在集群内部使用========================================只映射一个 9200 端口(即 es01)的奥妙在于——集群对外是一个整体,入口只需要一个:ES 集群对客户端是无状态的:任意节点都能接收请求并转发到正确位置客户端 ──> 19200 (es01) ──> 集群内部 9300 互相转发 ├── es01 ├── es02 └── es03`所以你写数据到 es01,如果数据本该在 es02,es01 会自动转发过去,客户端无感知`# 安全、精简、便于运维5)启动服务[root@Docker multiple-es-cluster]# docker compose up -d[+] Running 11/11 ✔ Network multiple-es-cluster_default Created 0.0s ✔ Volume "multiple-es-cluster_esdata02" Created 0.0s ✔ Volume "multiple-es-cluster_certs" Created 0.0s ✔ Volume "multiple-es-cluster_esdata03" Created 0.0s ✔ Volume "multiple-es-cluster_kibanadata" Created 0.0s ✔ Volume "multiple-es-cluster_esdata01" Created 0.0s ✔ Container multiple-es-cluster-setup-1 Healthy 5.2s ✔ Container multiple-es-cluster-es01-1 Healthy 86.9s ✔ Container multiple-es-cluster-es02-1 Healthy 86.8s ✔ Container multiple-es-cluster-es03-1 Healthy 97.2s ✔ Container multiple-es-cluster-kibana-1 Started 97.3s✅️ setup 容器只跑证书初始化,健康后 ES 节点才启动
6)查看容器状态[root@Docker multiple-es-cluster]# docker compose psNAME IMAGE COMMAND SERVICE STATUS PORTSmultiple-es-cluster-es01-1 elasticsearch:9.4.4 "/bin/tini…" es01 Up About a minute (healthy) 9300/tcp, 127.0.0.1:19200->9200/tcpmultiple-es-cluster-es02-1 elasticsearch:9.4.4 "/bin/tini…" es02 Up About a minute (healthy) 9200/tcp, 9300/tcpmultiple-es-cluster-es03-1 elasticsearch:9.4.4 "/bin/tini…" es03 Up About a minute (healthy) 9200/tcp, 9300/tcpmultiple-es-cluster-kibana-1 kibana:9.4.4 "/bin/tini…" kibana Up 2 seconds (health: starting) 0.0.0.0:15601->5601/tcp# 只有 es01 映射了 19200:9200,其余节点仅在集群内部通信7)验证ES集群root@Docker multiple-es-cluster# curl https://127.0.0.1:19200 -k -u elastic:passwd{ "name" : "es01", "cluster_name" : "My-ES", "cluster_uuid" : "4hKfGKl3TcKEgA2a-w0ZXQ", "version" : { "number" : "9.4.4", "build_flavor" : "default", "build_type" : "docker", ........... }, "tagline" : "You Know, for Search"}# 认证通过,ES 9.4.4[root@Docker multiple-es-cluster]# curl -sk -u elastic:passwd https://127.0.0.1:19200/_cat/nodes172.18.0.4 66 100 4 0.53 1.51 0.80 cdfhilmrstw * es02172.18.0.3 66 100 4 0.53 1.51 0.80 cdfhilmrstw - es01172.18.0.5 74 98 4 0.53 1.51 0.80 cdfhilmrstw - es03✅️ 三个节点都在,星号*是当前 master(es02)8)修改kibana语言为中文[root@Docker multiple-es-cluster]# docker exec -it multiple-es-cluster-kibana-1 /bin/bashkibana@4914a063e7e1:~$ echo '' >> config/kibana.yml# 先去换行 ’空‘kibana@4914a063e7e1:~$ echo i18n.locale: "zh-CN" >> config/kibana.ymlkibana@4914a063e7e1:~$ cat config/kibana.ymlserver.host: "0.0.0.0"elasticsearch.hosts: [ "http://elasticsearch:9200" ]i18n.locale: zh-CNkibana@4914a063e7e1:~$ exit[root@Docker multiple-es-cluster]# docker compose restart kibana Container multiple-es-cluster-kibana-1 Restarting Container multiple-es-cluster-kibana-1 Started✅️ 追加 i18n.locale: "zh-CN" 再重启,界面变中文
9)访问验证[root@Docker multiple-es-cluster]# curl -s -o /dev/null -w "%{http_code}\n" http://10.0.0.9:15601/200✅️ Kibana 正常运行访问: http://10.0.0.9:15601/用户名: elastic 密码: passwdNote
📌 为什么需要 env 变量文件?
- 密码、版本、端口这类”环境相关”的配置抽到
.env,与 compose 解耦 - 换环境/换版本时只改
.env,不动 compose 逻辑 ${STACK_VERSION}一处定义,3 个 ES 节点 + Kibana 全部生效
Services引用secrets

'环境准备'1)创建项目目录[root@Docker ~]# mkdir /root/wordpress-secrets && cd /root/wordpress-secrets
2)准备密码文件[root@Docker wordpress-secrets]# echo passwd > db_password.txt[root@Docker wordpress-secrets]# echo redhat > db_root_password.txt[root@Docker wordpress-secrets]# cat db_password.txtpasswd[root@Docker wordpress-secrets]# cat db_root_password.txtredhat# 数据库root用户的密码Tip
Docker Compose 的 secrets(机密)功能,用于给容器安全地传递密码、密钥等敏感信息:
- 把敏感数据抽到独立的文件里,compose 负责把它挂载到容器的
/run/secrets/目录 - 容器内的程序通过读取文件拿到密码,而不是写死在环境变量里
- 好处:密码不暴露在
docker inspect输出 / 镜像历史 / compose 文件中
3)编写 docker-compose.yaml[root@Docker wordpress-secrets]# vim docker-compose.yamlservices: db_server: image: mysql:8.0.36 container_name: db ports: - "3306:3306" environment: MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password MYSQL_DATABASE: "wordpress" MYSQL_USER: "jiuzhao" MYSQL_PASSWORD_FILE: /run/secrets/db_password command: ["--character-set-server=utf8", "--collation-server=utf8_bin", "--default-authentication-plugin=mysql_native_password"] volumes: - db:/var/lib/mysql:rw networks: - wp-net restart: unless-stopped # 定义服务引用secrets资源,并将其挂载到"/run/secrets/"目录下 secrets: - db_root_password - db_password
wordpress: depends_on: - db_server image: wordpress:6.7.1 container_name: wp restart: unless-stopped ports: - "81:80" environment: - WORDPRESS_DB_HOST=db_server:3306 - WORDPRESS_DB_USER=jiuzhao - WORDPRESS_DB_PASSWORD_FILE=/run/secrets/db_password - WORDPRESS_DB_NAME=wordpress volumes: - wp:/var/www/html networks: - wp-net secrets: - db_password
networks: wp-net: name: wordpress-net driver: bridge ipam: driver: default config: - subnet: 172.19.0.0/16 gateway: 172.19.0.1
# 定义secretssecrets: # 定义secret的名称便于Service进行引用 db_password: # 指定secret的文件名称 file: db_password.txt db_root_password: file: db_root_password.txt
volumes: db: name: mysql-volume wp: name: wordpress-volume========================================MYSQL_ROOT_PASSWORD_FILE: # 告诉mysql镜像去这个文件里读密码MYSQL_PASSWORD_FILE: # 普通用户密码也从文件读# ⭐ 这就是 secrets 的核心: 密码不写在 compose 里,而是挂载成文件secrets: # 服务级引用,把对应的secret挂到 /run/secrets/ 下4)启动服务[root@Docker wordpress-secrets]# docker compose up -d[+] Running 5/5 ✔ Network wordpress-net Crea... 0.0s ✔ Volume "mysql-volume" Crea... 0.0s ✔ Volume "wordpress-volume" Created 0.0s ✔ Container db Started 0.4s ✔ Container wp Started 0.5sroot@Docker wordpress-secrets# docker compose psNAME IMAGE COMMAND SERVICE CREATED STATUS PORTSdb mysql:8.0.36 "docker-entrypoint.s…" db_server 24 seconds ago Up 24 seconds 0.0.0.0:3306->3306/tcp, 33060/tcpwp wordpress:6.7.1 "docker-entrypoint.s…" wordpress 24 seconds ago Up 24 seconds 0.0.0.0:81->80/tcp
5)测试验证'用文件里的密码 redhat 登录mysql'[root@Docker wordpress-secrets]# docker exec db mysql -predhat -e "SHOW DATABASES;"Databaseinformation_schemamysqlperformance_schemasyswordpress✅️ 密码来自 /run/secrets/db_root_password 文件,root 登录成功6)查看文件的挂载信息'进db容器,看secrets都挂载了哪些文件'[root@Docker wordpress-secrets]# docker exec db bash -c "ls /run/secrets/"db_passworddb_root_password[root@Docker wordpress-secrets]# docker exec db bash -c "cat /run/secrets/db_password"passwd[root@Docker wordpress-secrets]# docker exec db bash -c "cat /run/secrets/db_root_password"redhat# db 引用了两个 secret,都能看到
'进wp容器,它只引用了 db_password 一个'[root@Docker wordpress-secrets]# docker exec wp bash -c "ls /run/secrets/"db_password[root@Docker wordpress-secrets]# docker exec wp bash -c "cat /run/secrets/db_password"passwd========================================✅️ 每个服务只挂载自己引用的 secret# wordpress 拿不到 db_root_password,最小权限原则Note
📌 secrets 解决了什么问题?
- 密码不再硬编码在
environment里 → 不出现在docker inspect输出中 - 文件挂载到
/run/secrets/按需引用,各服务只见自己该见的 - 环境变量用
*_FILE后缀指向 secrets 文件路径即可读取
Zabbix部署
[root@Docker ~]# rm -rf ./*[root@Docker ~]# mkdir zabbix-server && cd zabbix-server[root@Docker zabbix-server]# vim docker-compose.ymlservices: mysql: image: mysql:8.0.36 container_name: db restart: unless-stopped # 指定容器的重启策略 environment: MYSQL_ROOT_PASSWORD: "root_pwd" MYSQL_DATABASE: "zabbix" MYSQL_USER: "zabbix" MYSQL_PASSWORD: "passwd" command: ["--character-set-server=utf8", "--collation-server=utf8_bin", "--default-authentication-plugin=mysql_native_password"] volumes: - db:/var/lib/mysql:rw networks: - zabbix-net
zabbix-java-gateway: container_name: zabbix-java-gateway image: zabbix-java-gateway:alpine-7.2-latest restart: unless-stopped networks: - zabbix-net
zabbix-server: container_name: zabbix-server # 依赖哪个服务 depends_on: - mysql image: zabbix-server-mysql:alpine-7.2-latest restart: unless-stopped environment: DB_SERVER_HOST: mysql # 服务名 MYSQL_DATABASE: zabbix MYSQL_USER: zabbix MYSQL_PASSWORD: passwd MYSQL_ROOT_PASSWORD: "root_pwd" ZBX_JAVAGATEWAY: zabbix-java-gateway # 服务名 networks: - zabbix-net
zabbix-web-nginx-mysql: container_name: zabbix-web-nginx-mysql depends_on: - zabbix-server image: zabbix-web-nginx-mysql:alpine-7.2-latest ports: - "8080:8080" restart: unless-stopped environment: DB_SERVER_HOST: mysql MYSQL_DATABASE: zabbix MYSQL_USER: zabbix MYSQL_PASSWORD: passwd MYSQL_ROOT_PASSWORD: "root_pwd" networks: - zabbix-net
networks: zabbix-net: name: zabbix driver: bridge ipam: driver: default config: - subnet: 172.20.0.0/16 gateway: 172.20.0.1
volumes: db: name: zabbix-mysql-volume[root@Docker zabbix-server]# docker network ls[root@Docker zabbix-server]# docker volume ls[root@Docker zabbix-server]# docker compose up -d[+] up 6/6 ✔ Network zabbix Created ✔ Volume zabbix-mysql-volume Created ✔ Container zabbix-java-gateway Started ✔ Container db Started ✔ Container zabbix-server Started ✔ Container zabbix-web-nginx-mysql Started"一个网络,一个数据卷""4个容器"[root@Docker zabbix-server]# docker network inspect zabbix | grep IPv4Address "IPv4Address": "172.20.0.4/16", "IPv4Address": "172.20.0.2/16", "IPv4Address": "172.20.0.3/16", "IPv4Address": "172.20.0.5/16",# 4个容器的IP地址!
http://10.0.0.99:8080/
LB负载

-
请自定义3个镜像,要求使用alpine作为基础镜像
-
然后使用docker-compose一键启动
- 用户访问lb后,轮询方式访问web01和web02
- 比例为 3<1>1>
- 用户访问lb后,轮询方式访问web01和web02
🦄 我们使用Dockerfile写三个基础镜像
借鉴之前的笔记
1)环境清理docker compose down -t 0 -v[root@Docker ~]# rm -rf ./*[root@Docker ~]# mkdir lb-server && cd lb-server[root@Docker lb-server]# mkdir ./{conf,dockerfile}# 一个放配置文件,一个Dockerfile文件
2)Nginx配置文件准备[root@Docker lb-server]# vim ./conf/lb.conf# 负载均衡的配置文件 upstream webs { server web01 weight=3; server web02 weight=1;}server { listen 80; server_name diy.kpyun.com;
location / { proxy_pass http://webs; proxy_set_header Host $http_host; proxy_http_version 1.1; }}[root@Docker lb-server]# vim ./conf/default.conf# 两台web的配置文件server { listen 80 default_server; server_name _; root /code;
location / { index index.html; }}
3)Dockerfile文件编写[root@Docker lb-server]# vim ./dockerfile/lbFROM alpine:3.20.2EXPOSE 80RUN apk update && \ apk add nginx && \ rm -rf /var/cacheCOPY ./lb.conf /etc/nginx/http.d/default.confCMD ["nginx","-g","daemon off;"]- vim ./dockerfile/web01
FROM alpine:3.20.2EXPOSE 80RUN apk update && \ apk add nginx && \ rm -rf /var/cacheRUN mkdir /code && \ echo "web01..." > /code/index.htmlCOPY ./default.conf /etc/nginx/http.d/CMD ["nginx","-g","daemon off;"]- vim ./dockerfile/web02
FROM alpine:3.20.2EXPOSE 80RUN apk update && \ apk add nginx && \ rm -rf /var/cacheRUN mkdir /code && \ echo "web02..." > /code/index.htmlCOPY ./default.conf /etc/nginx/http.d/CMD ["nginx","-g","daemon off;"]4)生成镜像[root@Docker lb-server]# docker build -f ./dockerfile/lb -t my-lb:1.0 ./conf/[root@Docker lb-server]# docker build -f ./dockerfile/web01 -t my-web01:1.0 ./conf/[root@Docker lb-server]# docker build -f ./dockerfile/web02 -t my-web02:1.0 ./conf/[root@Docker lb-server]# docker imagesmy-lb:1.0 3292e8f72107 13.9MBmy-web01:1.0 3b28a35f13c7 13.9MBmy-web02:1.0 8d0461877ed5 13.9MB
5)docker-compose文件编写[root@Docker lb-server]# vim docker-compose.yml:services: lb: image: my-lb:1.0 container_name: lb restart: unless-stopped ports: - "90:80" networks: - webs-net
web01: container_name: web01 image: my-web01:1.0 restart: unless-stopped networks: - webs-net
web02: container_name: web02 image: my-web02:1.0 restart: unless-stopped networks: - webs-net
networks: webs-net: name: my_net driver: bridge ipam: driver: default config: - subnet: 172.21.0.0/16 gateway: 172.21.0.1[root@Docker lb-server]# docker compose up -d[+] up 4/4 ✔ Network my_net Created ✔ Container web02 Started ✔ Container web01 Started ✔ Container lb Started[root@Docker lb-server]# docker inspect lb | grep IPAddress "IPAddress": "172.21.0.2",[root@Docker lb-server]# tail -1 /etc/hosts172.21.0.2 diy.kpyun.com[root@Docker lb-server]# for i in `seq 10`;do curl diy.kpyun.com;doneweb01... ❤️web01... ❤️web01... ❤️web02... 💚web01... ❤️web01... ❤️web01... ❤️web02... 💚web01... ❤️web01... ❤️镜像仓库
什么是私有仓库❓️# 不对全世界开放的镜像仓库# 一般是公司内部使用的私有镜像仓库
为什么需要私有仓库❓️1)安全性问题# 官方镜像仓库大家都可以访问🌰 若都放在官方,大家都能拿到你们公司的自建镜像2)访问速度:🌰 官方的服务器在国外,访问速度慢==============================🔖私有仓库:1)docker registry:# 轻量级的私有镜像仓库# 基本上不占用内存,很适合学习环境中使用 ✅️ 会对存储的数据进行一定的压缩处理 ✅️ 占用内存不足20MB ❌️ 没有提供较好的WebUI ❌️ 维护起来比较麻烦,尤其是删除镜像2)harbor: 🦄 基于官方的"docker registry"进行二次开发 🦄 是一个适合企业级使用的镜像仓库🔖第三方仓库: --> 轩辕镜像站(docker.xuanyuan.run)🔖docker官方仓库: --> hub.docker.comdocker registry
"清理环境"[root@Docker ~]# rm -rf ./*docker rm -f $(docker ps -aq)docker network prune -fdocker volume prune -fa
1)拉取镜像[root@Docker ~]# docker pull docker.xuanyuan.run/registry:3.0.0# 自行改标签[root@Docker ~]# docker imagesregistry:3.0.0 6c5666b861f3 77.3MB
2)运行[root@Docker ~]# docker run -d --network host --restart=unless-stopped --name c1 -v img-volume:/var/lib/registry registry:3.0.0# 和宿主机共用一个网络# 用了一个数据卷[root@Docker ~]# docker volume lsDRIVER VOLUME NAMElocal img-volume[root@Docker ~]# docker ps registry:3.0.0 "/entrypoint.sh /etc…" c1[root@Docker ~]# ss -lntup | grep 5000"默认监听5000端口"tcp LISTEN 0 *:5000 users:(("registry",pid=8496,fd=3))# 默认提供了一个web-UI
浏览器访问:http://10.0.0.99:5000/v2/_catalog
[root@Docker ~]# curl http://10.0.0.99:5000/v2/_catalog{"repositories":[]}# 这个仓库没有任何的数据推送镜像
1)打tag标签🏷️[root@Docker ~]# docker tag alpine:3.20.2 10.0.0.99:5000/base/alpine:3.20.2'服务器地址(IP+端口)/项目名称/镜像'# 三个部分组成[root@Docker ~]# docker push 10.0.0.99:5000/base/alpine:3.20.2'直接推镜像!'Unavailable --> 不可达http: server gave HTTP response to HTTPS client⚠️ 服务端(搭建的私有仓库)响应HTTP给客户端(我们自己)HTTPS# 我们客户端默认使用HTTPS协议
2)强制客户端走HTTP(不安全的仓库)[root@Docker ~]# vim /etc/docker/daemon.json# 新建的文件{ "insecure-registries": ["10.0.0.99:5000"]}[root@Docker ~]# systemctl restart docker[root@Docker ~]# docker info | grep Registries -A 2"重启服务后,配置生效" Insecure Registries: 10.0.0.99:5000 ::1/128[root@Docker ~]# docker ps"重启服务后,仍然运行" --> 重启策略 unless-stoppedregistry:3.0.0 "/entrypoint.sh /etc…" c1[root@Docker ~]# ss -lntup | grep 5000# 仍然监听5000端口tcp LISTEN 0 *:5000 users:(("registry",pid=9278,fd=3))
3)再次推送镜像[root@Docker ~]# docker push 10.0.0.99:5000/base/alpine:3.20.2The push refers to repository [10.0.0.99:5000/base/alpine]c6a83fedfae6: "Pushed"[root@Docker ~]# curl http://10.0.0.99:5000/v2/_catalog✅️ {"repositories":["base/alpine"]}⚠️ 版本号被隐藏了
4)另一台机器拉取镜像[root@test ~]# docker pull 10.0.0.99:5000/base/alpine:3.20.2Error response from daemon:❌️ http: server gave HTTP response to HTTPS client# 一样的报错# 客户端使用的是HTTPS[root@test ~]# vim /etc/docker/daemon.json{ "insecure-registries": ["10.0.0.99:5000"]}[root@test ~]# systemctl restart docker[root@test ~]# docker info | grep Registries -A 2 Insecure Registries: 10.0.0.99:5000 127.0.0.0/8[root@test ~]# docker pull 10.0.0.99:5000/base/alpine:3.20.23.20.2: Pulling from base/alpineDigest: sha256:eddxxx... --> 签名✍️Status: Downloaded newer image for 10.0.0.99:5000/base/alpine:3.20.2👆 镜像地址'成功拉取'[root@test ~]# docker images10.0.0.99:5000/base/alpine 3.20.2删除镜像
[root@Docker ~]# hostname -I10.0.0.99# 切换回来[root@Docker ~]# docker exec -it c1 sh/ # cd /var/lib/registry/var/lib/registry # tree ././└── docker └── registry └── v2 ├── blobs │ └── repositories/var/lib/registry # cd docker/registry/v2/var/lib/registry/docker/registry/v2 # lsblobs repositories# 主要就是它俩Docker Registry ==存储结构==:
/var/lib/registry/docker/registry/v2/ 目录包含:
blobs/目录:- 存放实际的镜像层数据(layers)
- 文件以 SHA256 哈希值命名
- 包含镜像的所有层数据
repositories/目录:- 存放仓库元数据
- 包含标签、清单等信息
/var/lib/registry/docker/registry/v2 # du -sh ./*3.5M ./blobs20.0K ./repositories📌 alpine镜像原来的大小是 7.8MB --> 3.5M"Docker Registry 会对存储的数据进行一定的压缩处理"/var/lib/registry/docker/registry/v2 # ls repositories/base# 这个base就是我们起的项目名称
1)删除元数据/var/lib/registry/docker/registry/v2 # rm -rf repositories/base⚠️删除的是它里面的项目,而不是本身的repositories目录⚠️ ❌️ rm -rf repositories/ ✅️ rm -rf repositories/base[root@Docker ~]# curl http://10.0.0.99:5000/v2/_catalog{"repositories":[]}# 已经没有了'但真实的数据并没有删除'/var/lib/registry/docker/registry/v2 # du -sh ./*3.5M ./blobs0 ./repositories <-- 依旧存在这个目录✅️# 只是少了 20K 而已
2)回收镜像层数据# 彻底释放存储空间/var/lib/registry/docker/registry/v2 # registry garbage-collect /etc/distribution/config.yml/var/lib/registry/docker/registry/v2 # du -sh ./*0 ./blobs0 ./repositories"这下子就都干净了!"bug解决
"描述:" 🏷️ 已经推送的镜像无法再次推送(需重启registry容器) 🏷️ 尽管已经删除回收,推送时会有缓存============================================[root@Docker ~]# docker push 10.0.0.99:5000/base/alpine:3.20.2'再次推送刚彻底删除的镜像'The push refers to repository [10.0.0.99:5000/base/alpine]c6a83fedfae6: "Layer already exists" <-- 已经显示出异样了⚠️# 描述的是exists已存在,而不是Pushed已推送✅️ 因为刚删除不久,有缓存[root@Docker ~]# curl http://10.0.0.99:5000/v2/_catalog{"repositories":["base/alpine"]}# 这里尝试拉取一下页面,有它的元数据/var/lib/registry/docker/registry/v2 # du -sh ./*0 ./blobs12.0K ./repositories'切回去看容器内部数据'# 只有元数据,没有镜像数据"解决方案" 🦄 重启docker-registries容器即可[root@Docker ~]# docker restart c1'再次推送'[root@Docker ~]# docker push 10.0.0.99:5000/base/alpine:3.20.2The push refers to repository [10.0.0.99:5000/base/alpine]c6a83fedfae6: "Pushed"# 已推送[root@Docker ~]# curl http://10.0.0.99:5000/v2/_catalog{"repositories":["base/alpine"]}[root@Docker ~]# docker exec -it c1 sh/ # cd /var/lib/registry//var/lib/registry # cd docker/registry/v2/var/lib/registry/docker/registry/v2 # du -sh ./*3.5M ./blobs20.0K ./repositories✅️ '一切都回来了'[root@Docker ~]# docker pull 10.0.0.99:5000/base/alpine:3.20.2✅️ '拉取自己的镜像仓库'3.20.2: Pulling from base/alpineDigest: sha256:eddxxxStatus: Downloaded newer image for 10.0.0.99:5000/base/alpine:3.20.2[root@Docker ~]# docker images10.0.0.99:5000/base/alpine:3.20.2 eddxxx 11.7MBharbor
Harbor 是 VMware 公司开源的==企业级镜像仓库==,主要特点如下:
-
提供安全、可靠的私有镜像存储和分发
-
底层使用 Docker Compose 来管理 Harbor 服务
-
提供图形化管理界面(Web UI)
GitHub 地址:https://github.com/goharbor/harbor
- 离线安装
- 这里==建议用v2.14版本==
- 复制标签没有bug


1)上传解压[root@Docker ~]# cd /tmp[root@Docker tmp]# lsharbor-offline-installer-v2.14.3.tgz[root@Docker tmp]# tar xf harbor-offline-installer-v2.14.3.tgz -C /usr/local/
2)修改配置文件[root@Docker tmp]# cd /usr/local/harbor/[root@Docker harbor]# lscommon.sh harbor.v2.14.3.tar.gz harbor.yml.tmpl ...[root@Docker harbor]# cp harbor.yml{.tmpl,}# 把备份文件复制出来"把后缀tmpl去掉"[root@Docker harbor]# ls...xxx harbor.yml.tmpl "harbor.yml"[root@Docker harbor]# vim harbor.ymlhostname: 10.0.0.99http: port: 80# 后面再启用HTTPSharbor_admin_password: passwd# 修改登录密码database: password: root123 max_idle_conns: 100 max_open_conns: 900 conn_max_lifetime: 5m conn_max_idle_time: 0data_volume: /var/lib/harbor# 更改数据卷目录# 如果卸载harbor的话,要把这个目录手动删除掉trivy: ignore_unfixed: false skip_update: false skip_java_db_update: false db_repository: ghcr.io/aquasecurity/trivy-db java_db_repository: ghcr.io/aquasecurity/trivy-java-db offline_scan: false security_check: vuln insecure: false timeout: 5m0sjobservice: max_job_workers: 10 max_job_duration_hours: 24 job_loggers: - STD_OUTPUT - FILE logger_sweeper_duration: 1notification: webhook_job_max_retry: 3 webhook_job_http_client_timeout: 3log: level: info local: rotate_count: 50 rotate_size: 200M location: /var/log/harbor_version: 2.15.0proxy: http_proxy: https_proxy: no_proxy: components: - core - jobservice - trivyupload_purging: enabled: true age: 168h interval: 24h dryrun: falsecache: enabled: false expire_hours: 243)安装harbor服务[root@Docker harbor]# ./install.sh# 底层就是用shell脚本写的[Step 5]: starting Harbor ...[+] up 10/10 ✔ Network harbor_harbor Created ✔ Container harbor-log Started ✔ Container harbor-db Started ✔ Container registryctl Started ✔ Container harbor-portal Started ✔ Container redis Started ✔ Container registry Started ✔ Container harbor-core Started ✔ Container harbor-jobservice Started ✔ Container nginx Started✅️ Harbor has been installed and started successfully.[root@Docker harbor]# docker compose lsNAME STATUS CONFIG FILESharbor running(9) /usr/local/harbor/docker-compose.yml
4)测试验证http://10.0.0.99/✅️用户名: admin✅️密码: passwd
新建项目


'推送命令'1)先打标记[root@Docker harbor]# docker tag alpine:3.20.2 10.0.0.99/kpyun/alpine:3.20.2
2)推送[root@Docker harbor]# docker push 10.0.0.99/kpyun/alpine:3.20.2dial tcp 10.0.0.99:443: connect: connection refused'还是老问题'# 客户端默认连接HTTPS:443端口
3)改配置&重启服务[root@Docker harbor]# vim /etc/docker/daemon.json{ "insecure-registries": ["10.0.0.99:5000","10.0.0.99"]}# 逗号分隔'修改Docker配置文件,需要重启服务'[root@Docker harbor]# systemctl restart docker[root@Docker harbor]# docker compose ps -aExited (128)........UpExited (128)Exited (128)'发现很多容器都异常退出了!'
4)停止再重启✅️ 常见的解决办法![root@Docker harbor]# docker compose down -t 0'小细节 --> 没有-v --> 保留数据卷'# 刚才创建的项目会保留下来[root@Docker harbor]# docker compose up -d# 再次启动容器
5)再次推送[root@Docker harbor]# docker push 10.0.0.99/kpyun/alpine:3.20.2authorization failed --> 认证失败,没有权限
6)需要先进行登录[root@Docker harbor]# docker login 10.0.0.99 -u admin -p passwd# 指定服务器地址,默认是Docker Hub# 我这里同时指定了密码Your credentials are stored unencrypted in '/root/.docker/config.json'.# 被存储在这个文件中[root@Docker harbor]# cat /root/.docker/config.json{ "auths": { "10.0.0.99": { "auth": "YWRtaW46cGFzc3dk" } }}# 我们可以通过base64把它解码出来[root@Docker harbor]# echo YWRtaW46cGFzc3dk | base64 -dadmin:passwd'用户名:密码'
7)再次推送[root@Docker harbor]# docker push 10.0.0.99/kpyun/alpine:3.20.2The push refers to repository [10.0.0.99/kpyun/alpine]c6a83fedfae6: "Pushed"# 这次就推送上去了

8)测试验证'切另一台机器验证!'[root@test ~]# vim /etc/docker/daemon.json{ "insecure-registries": ["10.0.0.99:5000","10.0.0.99"]}[root@test ~]# systemctl restart docker[root@test ~]# docker login 10.0.0.99 -u admin -p passwdLogin Succeeded[root@test ~]# docker pull 10.0.0.99/kpyun/alpine:3.20.2'刚才复制的命令 <-- web页面'[root@test ~]# docker images10.0.0.99/kpyun/alpine 3.20.2
9)密码保护# 在登录的时候,你的密码被保存[root@test ~]# cat /root/.docker/config.json{ "auths": { "10.0.0.99": { "auth": "YWRtaW46cGFzc3dk" } }}⚠️ 容易被盗窃!# 养成习惯,不用的时候,退出登录状态[root@test ~]#docker logout 10.0.0.99Removing login credentials for 10.0.0.99'后接镜像仓库的网址'[root@test ~]# cat /root/.docker/config.json{ "auths": {}}✅️ 现在里面就没有密码信息了镜像仓库迁移
项目背景: 早期的docker registry镜像管理不方便 后续将其迁移到harbor仓库项目步骤: 1.配置仓库 2.新建复制规则 3.启动复制规则 4.设置项目为公开 5.测试验证==================================1)环境准备[root@Docker harbor]# docker run -d --network host --restart=unless-stopped --name c1 -v img-volume:/var/lib/registry registry:3.0.0[root@Docker harbor]# ss -lntup | grep 50004096 *:5000 users:(("registry",pid=76702,fd=6))[root@Docker harbor]# curl localhost:5000/v2/_catalog{"repositories":["base/alpine"]}# 复用的数据卷,所以数据并没有丢'再推几个镜像'[root@Docker harbor]# docker tag nginx:latest 10.0.0.99:5000/base/nginx:latest[root@Docker harbor]# docker tag mysql:8.0.36 10.0.0.99:5000/base/mysql:8.0.36# 打标签,后推送[root@Docker harbor]# docker push 10.0.0.99:5000/base/nginx:latest[root@Docker harbor]# docker push 10.0.0.99:5000/base/mysql:8.0.36[root@Docker harbor]# curl localhost:5000/v2/_catalog{"repositories":["base/alpine","base/mysql","base/nginx"]}
2)harbor配置仓库
- 新建复制规则

- 启动复制规则



- 设置项目为公开



- 测试验证
[root@test ~]# docker login 10.0.0.99 -u admin -p passwd'先去项目中复制命令'[root@test ~]# docker pull 10.0.0.99/base/mysql:8.0.36# 这样更简便Status: Downloaded newer image for 10.0.0.99/base/mysql:8.0.36✅️ 成功拉取镜像主从同步
1)在另一个节点安装harbor'test节点'# 过程省略[root@test /usr/local/harbor]# ./install.sh[root@test /usr/local/harbor]# docker compose up -d[+] Running 10/10[root@test /usr/local/harbor]# vim /etc/docker/daemon.json{"insecure-registries": ["10.0.0.99:5000","10.0.0.128"]}⚠️ 指向自己[root@test /usr/local/harbor]# systemctl restart docker[root@test /usr/local/harbor]# docker compose lsNAME STATUS CONFIG FILESharbor running(9) /usr/local/harbor/docker-compose.yml[root@test /usr/local/harbor]# docker login 10.0.0.128 -u admin -p passwdLogin Succeeded
2)访问测试http://10.0.0.128/
-
主镜像仓库(.99) —> 备用(.128)
-
配置仓库

- 复制规则管理

- 等一分钟 …
- 验证备用仓库是否同步

- ✅️ 成功同步
==🤔疑问==: 如果备用仓库也被创建了项目,会同步至主仓库嘛❓️
- 🌿自然不会 ✅️
- 我们设置的规则是 —> 主同步至从库
- 每分钟 push推送 一次
- 我们设置的规则是 —> 主同步至从库
'在从库创建项目,随便推几个镜像'📌 先在web页面中创建项目# 从库中[root@test /usr/local/harbor]# docker push 10.0.0.128/haha/busybox:latestThe push refers to repository [10.0.0.128/haha/busybox]068f50152bbc: "Pushed"
✅️ '主库这个时候是没有这个项目的!'--> 设置新的"复制规则"# 又来一个
- 等一会主库也有了
基于事件

相比传统的定时触发, 它实现了近实时、主动推送的同步机制
- 目前实验 —> 仅在push推送时可供选择
- pull拉取没有这个选项
| 特性 | 定时复制(旧方式) | 事件驱动复制(新方式) |
|---|---|---|
| 触发机制 | 按固定时间间隔(如每分钟)轮询主库变更 | 主库在发生关键事件时主动通知复制任务(如 push/delete/tag) |
| 延迟 | 最高可达配置间隔(如 1 分钟) | 秒级甚至毫秒级(取决于网络与系统负载) |
| 资源开销 | 周期性扫描,可能重复检查无变更内容 | 仅在真实事件发生时触发,更高效 |
- 修改复制规则
- 主同步至从库
- 每分钟 push推送 一次 —> ==基于事件触发==
- 主同步至从库


- 删除测试


- ==增加镜像测试==
[root@Docker harbor]# docker tag redhat-linux-tools:v0.1 10.0.0.99/kpyun/redhat-linux-tools:v0.1[root@Docker harbor]# docker push 10.0.0.99/kpyun/redhat-linux-tools:v0.1'推送至主库'
# 很快也会同步至从库✅️ 你主库一推,立马同步至从库
高可用

- 把所有的镜像推送至一个地方(NFS)
- 宿主机之间keepalived
- 镜像之间互相同步(pull & push) <— 官方推荐
- 👆就是我们演示的==主从同步==
- 宿主机之间keepalived
1)安装keepalived# 两个节点都安装[root@Docker ~]# dnf -y install keepalived[root@test ~]# yum -y install keepalived
2)修改配置文件[root@Docker ~]# vim /etc/keepalived/keepalived.confglobal_defs { router_id 10.0.0.99}
vrrp_instance VI_1 { interface eth0 virtual_router_id 50 priority 150 advert_int 1
authentication { auth_type PASS auth_pass 1111 }
virtual_ipaddress { 10.0.0.3 }}[root@test ~]# vim /etc/keepalived/keepalived.confglobal_defs { router_id 10.0.0.128}
vrrp_instance VI_1 { interface eth0 virtual_router_id 50 priority 100 advert_int 1
authentication { auth_type PASS auth_pass 1111 }
virtual_ipaddress { 10.0.0.3 }}'虚拟Mac地址就先不开了'# 我这里一个Centos,一个Rocky,虚拟Mac地址显示不出来
3)重启服务[root@Docker ~]# systemctl restart keepalived[root@test ~]# systemctl restart keepalived'10.xxx.99的优先级更高 --> 有虚拟IP'[root@Docker ~]# ip a2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 link/ether 00:0c:29:1c:7e:be brd ff:ff:ff:ff:ff:ff altname enp3s0 altname ens160 altname enx000c291c7ebe inet 10.0.0.99/24 brd 10.0.0.255 scope global noprefixroute eth0 valid_lft forever preferred_lft forever inet 10.0.0.3/32 scope global eth0 valid_lft forever preferred_lft forever
4)测试验证http://10.0.0.3/'此时映射到10.xxx.99'
5)推送测试[root@Docker harbor]# docker tag redis:7.2.8 10.0.0.3/kpyun/redis:7.2.8[root@Docker harbor]# vim /etc/docker/daemon.json[root@Docker harbor]# systemctl restart docker[root@Docker harbor]# docker compose down -t 0[root@Docker harbor]# docker compose up -d[root@Docker harbor]# docker login 10.0.0.3 -u admin -p passwd[root@Docker harbor]# docker push 10.0.0.3/kpyun/redis:7.2.8'此时实际是推送至主库中'# 因为有复制规制,也会复制到从库中

6)VIP漂移'关闭 stop 主库的keepalived'[root@Docker harbor]# systemctl stop keepalived.service[root@test /usr/local/harbor]# ip a2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 00:0c:29:ec:ba:7b brd ff:ff:ff:ff:ff:ff inet 10.0.0.128/24 brd 10.0.0.255 scope global eth0 valid_lft forever preferred_lft forever inet 10.0.0.3/32 scope global eth0 valid_lft forever preferred_lft forever'漂移至从库中'
7)再次推送测试'因为10.xxx.99里有对应的配置,我们还在那里推送'[root@Docker harbor]# docker tag ubuntu:22.04 10.0.0.3/kpyun/ubuntu:22.04[root@Docker harbor]# docker push 10.0.0.3/kpyun/ubuntu:22.04The push refers to repository [10.0.0.3/kpyun/ubuntu]f63eb04151bc: "Pushed"
8)启动keepalived[root@Docker harbor]# systemctl start keepalived'优先级不同,抢占回来!'inet 10.0.0.99/24 brd 10.0.0.255 valid_lft forever preferred_lft foreverinet 10.0.0.3/32 scope global eth0HTTPS证书配置

1)上传证书[root@Docker harbor]# pwd/usr/local/harbor[root@Docker harbor]# unzip /tmp/24850683_docker.kpyun.fun_nginx.zip -d /usr/local/harbor/Archive: /tmp/24850683_docker.kpyun.fun_nginx.zipAliyun Certificate Download inflating: /usr/local/harbor/docker.kpyun.fun.pem inflating: /usr/local/harbor/docker.kpyun.fun.key[root@Docker harbor]# ls | grep docker.kpyun.docker.kpyun.fun.keydocker.kpyun.fun.pem
2)修改配置文件'主机名和https'[root@Docker harbor]# vim harbor.ymlhostname: docker.kpyun.funhttps: port: 443 certificate: /usr/local/harbor/docker.kpyun.fun.pem private_key: /usr/local/harbor/docker.kpyun.fun.key
3)清理环境&重新安装[root@Docker harbor]# docker compose down -t 0[root@Docker harbor]# ./prepare[root@Docker harbor]# ./install.sh
4)强制走HTTPS[root@Docker harbor]# vim /etc/docker/daemon.json{ "insecure-registries": []}# 都删除掉[root@Docker harbor]# systemctl restart docker[root@Docker harbor]# docker compose down -t 0[root@Docker harbor]# docker compose up -d[root@Docker harbor]# docker login docker.kpyun.fun -u admin -p passwd# 重新登录
5)映射&打标签[root@Docker harbor]# tail -1 /etc/hosts10.0.0.3 docker.kpyun.fun[root@Docker harbor]# docker tag centos:latest docker.kpyun.fun/kpyun/centos:latest[root@Docker harbor]# docker push docker.kpyun.fun/kpyun/centos:latestThe push refers to repository [docker.kpyun.fun/kpyun/centos]174f56854903: "Pushed"
6)Windows映射10.0.0.3 docker.kpyun.fun# 自动跳转 80 --> 443
脚本优化
[root@Docker harbor]# cd ~[root@Docker ~]# vim build.sh#!/bin/bash# 可以是写好的Dockerfile --> 自动build构建# 如果是Dockerfile,在构建的同时,就要打上自己私有仓库的地址!# 自动打标签,自动推送docker tag redhat-games:v0.6 docker.kpyun.fun/kpyun/redhat-games:v0.6docker login -u admin -p passwd docker.kpyun.fundocker push docker.kpyun.fun/kpyun/redhat-games:v0.6docker logout docker.kpyun.fun[root@Docker ~]# sh build.shLogin Succeeded --> 成功登录The push to repository [docker.kpyun.fun/kpyun/redhat-games] --> 推送镜像5dcfac0f2f9c: "Pushed".......Removing login credentials for docker.kpyun.fun --> 退出登录
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
相关文章智能推荐
1
传统业务容器化
Docker容器以RuoYi-Vue为例演示Java项目全流程容器化,涵盖Maven打包、Vue前端与Compose多服务编排部署
2
Docker结课实践考核
Docker容器Docker容器技术结课实践考核,涵盖容器化部署、编排及运维实战
3
Dockerfile详解
Docker容器详解Dockerfile全部核心指令与ENTRYPOINT/CMD配合机制,实战多阶段构建与镜像优化
4
Linux底层特性&&初识Dockerfile
Docker容器手操chroot与OverlayFS分层结构,实践cgroup资源限制与namespace隔离,初识Dockerfile构建
5
数据卷&&网络
Docker容器讲解数据卷挂载方式与docker0网桥原理,掌握四种网络类型及自定义DNS,实战多项目部署















