☰
Docker容器实战经验全解析:从安装部署到云原生落地
2026/10/3 3:45:35 网站建设 项目流程

写 Docker 这些年,我从踩坑里攒下的经验,今天一次性聊透。先说背景:我从 2015 年就开始把线上服务往容器里搬,从最初“跑个 MySQL 试试”,到现在本地开发、CI/CD 打包、微服务上线、GPU 推理部署,几乎没有哪天不用 Docker。而云原生这个概念,很多朋友觉得只有大厂才有资格聊,其实没那么多玄学,云原生的核心就是让应用和运行环境解耦,同一份代码从开发机到测试机再到生产集群,行为保持一致。做到这一步,最简单的切入点就是 Docker 容器技术。这篇文章不罗列官方文档,只讲我在安装 Docker、部署 MySQL 8.0、搭建 Redis 主从、打包微服务镜像、跑 AI 模型推理,以及处理各种容器网络和权限问题时踩过的真实坑,整理成一份能跟着直接动手的实操笔记。适合刚接触容器的小白,也适合已经用了 Docker 但常被启动失败、网络不通、镜像下载慢折腾的人。

1. 云原生为什么要从 Docker 讲起

1.1 云原生的核心是“让应用换个环境也能活”

云原生这个词被说得太多了,反而容易让人忽略本质。它是一套构建和运行应用的方法,涉及微服务、容器、编排、CI/CD、可观测性等一大堆东西。但所有这些能力最终都要落在同一件事上:让应用不再依赖某个特定的物理机器或操作系统。

我之前遇到过最典型的场景是这样的。开发在本地把功能测得好好的,部署到测试服务器上,先是报缺库,再是版本不对,最后发现连时区都不一样。这种“在我电脑上是好的”现象,原因就是运行环境太不可控。Docker 把应用连同它的依赖、配置、运行参数一起打包成镜像,等于给应用造了一个随身携带的操作系统环境。无论它飘到哪台机器上,只要内核兼容且有容器运行时,跑起来的结果就是一致的。

所以在云原生体系里,Docker 更像是一块最底层的地基。它不负责微服务怎么拆,也不负责流量怎么调度,只负责解决环境一致性和部署标准化这两个最让人头疼的问题。

1.2 容器和虚拟机:差的不仅是性能

很多人刚接触容器时都会问,虚拟机的隔离性也很好,为什么非要容器?我把两种方案的对比列过很多次,归根结底是成本差异太大。

对比项虚拟机Docker 容器
启动时间秒级到分钟级毫秒级到秒级
镜像大小通常几个 GB通常几十 MB 到几百 MB
内存占用每个虚拟机都要完整系统共享宿主机内核,额外开销极小
隔离粒度内核级隔离进程级隔离
环境一致性取决于镜像制作镜像本身自带完整环境

虚拟机相当于给每个应用配了一台独立的电脑,电脑里得有 CPU、内存、硬盘、系统,哪怕应用只用一个端口,这些成本也省不掉。容器则像是给应用包了一个标准集装箱,集装箱里只有应用和它最需要的依赖,外面共享同一艘船的发动机和舱位。

这带来的直接影响是密度。同样一台 16G 内存的服务器,虚拟机可能只敢跑三四个实例,容器可以跑二十个甚至更多。我后面会讲到的 N100 小主机跑 20 个 Docker,靠的就是容器这种低开销特性。

1.3 Docker 不等于 Kubernetes,但它是第一公里

有个常见的误解是,既然上了云原生,就应该直接上 Kubernetes,Docker 好像已经退居幕后了。这个说法有一定道理,但还是不够准确。Kubernetes 是容器编排系统,它管理的底层运行时早期就是 Docker,现在生产环境很多已经使用 containerd 或 CRI-O,但开发阶段、镜像构建阶段,Docker 依然是事实标准。

哪怕到了 K8s 环境里,开发者日常接触的仍然是 Docker 这一套操作:docker build 构建镜像、docker push 推送到仓库、docker pull 拉取镜像。所以我始终建议个人和中小团队,不要一上来就啃 Kubernetes,先把 Docker 的镜像、容器、数据卷、网络这几个基本概念吃透,后面的编排之路会顺很多。

Docker 的核心其实就三个角色:镜像就像是应用的只读模板,容器是这个模板运行起来的实例,镜像仓库则是用来分发和共享模板的地方。理解这三者关系,后面所有操作都能套得进去。

2. 安装 Docker 的坑,我替你踩过

2.1 Windows 安装 Docker Desktop 前,先把虚拟化搞定

Windows 上安装 Docker 最常用的方式就是 Docker Desktop,但很多新手安装完双击启动,立刻被一句报错劝退:Docker Desktop failed to start because virtualisation support wasn't detected。

这个报错我保守估计处理过几十次了,原因高度集中在两处。

第一,物理机 BIOS 里没有开启虚拟化功能。开机进 BIOS,找 Intel Virtual Technology 或 SVM Mode,不同主板叫法不一样,设为 Enabled。开机后打开任务管理器,性能选项卡里能看到“虚拟化:已启用”的字样,就可以排除这个原因了。

第二,Windows 功能里缺少必要的虚拟机平台和 WSL 支持。Docker Desktop 在 Windows 上优先依赖 WSL2,如果系统里没有打开相关功能,启动同样会失败。正确步骤是:控制面板 -> 程序 -> 启用或关闭 Windows 功能,勾选“适用于 Linux 的 Windows 子系统”和“虚拟机平台”,然后以管理员身份运行 PowerShell,执行wsl --set-default-version 2,重启系统。

我建议直接用 WSL2 后端,不要用 Hyper-V。WSL2 启动更快,内存占用更小,跟 Windows 文件系统交互也更自然。装完 Docker Desktop 后,可以在设置里确认一下 Use the WSL 2 based engine 是否勾选。

2.2 Linux 安装 Docker 和 CentOS 7 版本升级

Linux 服务器上安装 Docker 相对简单,Ubuntu 系用官方脚本最省事:

curl -fsSL https://get.docker.com | bash -

装完立刻设置开机自启,这一步很多人会漏:

systemctl enable docker systemctl start docker docker info

CentOS 7 是比较特殊的情况。系统自带的旧版本 Docker 会被直接标识为docker,但版本号停留在 1.13 附近,很多新镜像和 Compose 功能根本不兼容。我实际操作中一般先卸载旧包,再通过官方 yum 仓库装 docker-ce:

yum remove docker docker-common docker-selinux docker-engine yum install -y yum-utils yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo yum install -y docker-ce docker-ce-cli containerd.io

需要提醒的是,CentOS 7 默认内核是 3.10,对 overlay2 存储驱动和 iptables 的新特性支持不太完善,能跑但偶尔会有诡异问题。生产环境我更推荐 Ubuntu 20.04 以上或较新的 Debian 版本,内核新,兼容性少很多。如果必须留在 CentOS 7,把内核升级到 4.x 或 5.x 能明显改善稳定性。

2.3 镜像加速与仓库配置的基本常识

Docker 默认从 Docker Hub 拉镜像,但实际使用中拉取速度经常不稳定,特别是较大的镜像。

解决办法是给 Docker 配置 registry mirror,也就是镜像加速地址。编辑/etc/docker/daemon.json:

{ "registry-mirrors": ["https://your-mirror-address"] }

Linux 改完重启 Docker:

systemctl daemon-reload systemctl restart docker

Windows Docker Desktop 则在 Settings -> Docker Engine 里改同样的 JSON,保存后它自动重启引擎。

这里有个细节我特别想说:镜像加速只对配置了该地址的客户端有效,它本质上是把常用镜像缓存到离你更近的节点,并不能保证所有镜像都能加速。遇到冷门镜像依然慢的时候,优先选择带具体版本号的 tag,比如mysql:8.0.36,而不是latest,不仅版本可控,还能避免每次拉取都解析到不同大小。

2.4 启动失败和权限报错的处理思路

Linux 下最常见的两个报错,第一个是Cannot connect to the Docker daemon,第二个是docker: permission denied。

前者的排查思路是,先确认服务是否真的在运行:

systemctl status docker journalctl -u docker -n 50

如果日志里有Failed to start Docker Application Container Engine,常见原因包括:磁盘满了、SELinux 拦截、配置文件写错导致 daemon 起不来、或者 containerd 版本冲突。依次检查磁盘剩余空间、临时关闭 SELinux 测试、把 daemon.json 改名后重启。大多数情况两三步就能定位。

后者则是典型的用户权限问题。Docker 默认只有 root 能直接操作,普通用户需要加入 docker 组:

sudo usermod -aG docker $USER

命令执行后一定要重新登录终端再试,否则组关系不生效,这是新手最容易困惑的点。

顺手提一个安全常识:加入 docker 组等同于获得宿主机 root 权限,因为容器的本质是共享内核,不应该在多人共享的服务器上随意给普通用户开这个权限。

3. 常用服务容器化的正确姿势

3.1 MySQL 8.0 容器化:没有持久化等于白装

MySQL 8.0 的容器化是我最常被问到的一个需求。先给一条完整命令:

docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=YourPassword \ -e TZ=Asia/Shanghai \ -v mysql-data:/var/lib/mysql \ mysql:8.0.36

命令拆开看,端口映射-p让宿主机可以直接访问,环境变量MYSQL_ROOT_PASSWORD是首次初始化时设置 root 密码,时区变量TZ解决了国内服务器普遍遇到的时区不一致问题,最关键的是-v mysql-data:/var/lib/mysql这种具名卷挂载。

为什么说没有持久化等于白装?因为容器可以随时删除重建,如果数据存在容器可写层里,docker rm之后数据就彻底没了。具名卷由 Docker 管理,存放在固定目录,即使容器删了,卷还在,重新运行同一条命令,数据自动回来了。

日常备份我就在宿主机上用docker exec配合 mysqldump 完成:

docker exec mysql8 sh -c 'exec mysqldump --all-databases -uroot -p"$MYSQL_ROOT_PASSWORD"' > backup.sql

另外提醒一个初始化细节:MySQL 官方镜像会在首次启动时自动执行/docker-entrypoint-initdb.d目录下的.sql和.sh脚本,用来初始化表结构非常方便。把建表脚本挂载进去,比启动后手动执行靠谱得多。

3.2 Redis 主从复制:从单机到主从只需几步

Redis 容器化有个小坑:默认配置文件里bind 127.0.0.1,容器里只监听回环地址,宿主机和别的容器都访问不到。我的做法是写一份自己的 redis.conf 挂载进去。

假设在/opt/redis下准备主从两份配置:

主节点 6379 基本配置:

port 6379 bind 0.0.0.0 protected-mode yes appendonly yes

主节点启动:

docker run -d --name redis-master \ -p 6379:6379 \ -v /opt/redis/master.conf:/etc/redis/redis.conf \ redis:7.2 redis-server /etc/redis/redis.conf

从节点只需要在配置文件末尾加一行:

replicaof 192.168.1.10 6379

然后启动从节点,注意端口别冲突:

docker run -d --name redis-slave \ -p 6380:6379 \ -v /opt/redis/slave.conf:/etc/redis/redis.conf \ redis:7.2 redis-server /etc/redis/redis.conf

验证是否成为从节点:

docker exec redis-slave redis-cli info replication

输出里role:slave且master_link_status:up就说明主从同步已经建立。如果想完整模拟生产环境,再加一个 redis-sentinel 容器做故障切换,这正是很多面试里问到的“Redis 主从哨兵”架构,用 Docker 三条命令就能搭好演练环境。

3.3 Docker Compose 编排:让多服务一步到位

单条 docker run 命令能解决的问题有限,真实业务至少是应用加数据库加缓存三个服务。这时候 Compose 的价值就体现出来了。

一个典型的 Spring Boot 项目配合 MySQL 和 Redis,docker-compose.yml长这样:

services: mysql: image: mysql:8.0.36 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: appdb volumes: - mysql-data:/var/lib/mysql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 10s retries: 5 redis: image: redis:7.2 command: redis-server --appendonly yes app: build: . depends_on: mysql: condition: service_healthy redis: condition: service_started ports: - "8080:8080" environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/appdb SPRING_DATA_REDIS_HOST: redis volumes: mysql-data:

启动命令只有一行:

docker compose up -d

这里的核心逻辑有两个。第一,Compose 自动创建了一个自定义网络,服务之间可以直接用服务名互相访问,不需要查 IP,应用配置里的mysql和redis就是容器主机名。第二,depends_on加condition能控制启动顺序,等 MySQL 真正健康了再启动应用,避免出现“应用起来了但连不上库”的尴尬。

我建议从今天起,任何项目的环境搭建都用 Compose 管理,哪怕只有一个服务。版本管理、启停、日志查看都方便,而且docker compose down一条命令就能清理干净,不会在系统里留下一堆手动创建的容器残留。

3.4 Spring Boot 微服务打包镜像:多阶段构建是关键

把 Spring Boot 项目打成 Docker 镜像,我踩过最典型的坑是镜像体积失控。如果用传统方式先把整个 JDK 装进镜像,再拷贝 jar 包,一个镜像随随便便五六百兆,传输和启动都慢。

正确做法是多阶段构建,用 IDEA 或 Maven 打包后,生成一个只有 JRE 的精简镜像。参考 Dockerfile:

FROM maven:3.9-eclipse-temurin-17 AS builder WORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests FROM eclipse-temurin:17-jre WORKDIR /app COPY --from=builder /build/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]

第一阶段的 maven 镜像只负责编译,第二阶段用一个清瘦的 JRE 镜像运行,最终镜像可能只有一百多兆。我把这称为“编译的归编译,运行的归运行”,理解这一点后,你就不会再写出臃肿的镜像了。

IDEA 里打包镜像也简单,装好 Docker 插件后,在运行配置里选择 Dockerfile,指定好镜像名和上下文目录,点运行就能直接 build。日常开发时,我习惯在本地先把服务容器跑通,再推到仓库给测试环境用,整个流程跟生产发布几乎一样。

打包时还要准备.dockerignore,排除target、.git、*.iml这些无关文件,否则构建上下文太大会让每次构建都变得很慢。

3.5 自托管应用和图腾般的“万能容器”

容器化最大的爽点,其实是部署各种开源系统变得像填空一样简单。

GitLab 是研发团队必备,用 Docker 装起来比裸机装省事太多:

docker run -d --name gitlab \ -p 8929:80 \ -v gitlab-config:/etc/gitlab \ -v gitlab-logs:/var/log/gitlab \ -v gitlab-data:/var/opt/gitlab \ gitlab/gitlab-ce:latest

注意 GitLab 很吃内存,至少 4G,否则常会卡到无法访问。

Metabase 搭 BI 分析平台,一条命令起服务,连上 MySQL 就能做可视化;KodBox 做私有网盘,挂载本地目录就能管理文件;OpenObserve 做日志和可观测性平台,取代传统的 Elasticsearch 全家桶,配置量小一个数量级。这类工具的共同特点是:官方镜像已经默认了最佳启动参数,我们只需要负责数据卷和端口映射,升级时换一个 tag 重新创建容器,数据完好无损。

青龙定时任务管理面板也是很多人用的容器化工具,它的依赖安装放在容器内部完成,换机器只需要重新跑容器,不需要再折腾宿主机上的 Node 或 Python 环境。这种“依赖跟容器走”的做法,正是容器技术能横扫自托管场景的根本原因。

我还见过用 Docker 快速启动 DVWA 做安全教学环境、用 Hadoop 镜像搭建大数据实验环境的情况,价值都一样:用完就丢,不污染宿主机,下次需要再拉起来,五分钟恢复原状。

4. 高阶玩法:GPU、ROS2 与小主机极限部署

4.1 让 GPU 容器真正用上显卡

跑 AI 模型推理时,Docker 就不仅是方便,而是非常有必要的。宿主机上一堆 CUDA 版本、Python 依赖、推理框架,互相打架是常事。容器化之后,每个模型服务各自独立,谁都不用迁就谁。

要让 GPU 容器正常工作,前提是宿主机已安装 NVIDIA 驱动,先验证:

nvidia-smi

然后安装 NVIDIA Container Toolkit,Ubuntu 系统的安装命令大致是:

curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | sed 's#deb https:#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https:#g' | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list sudo apt-get update sudo apt-get install -y nvidia-container-toolkit

之后运行任何带 GPU 的容器,加上--gpus all参数即可:

docker run --rm --gpus all nvidia/cuda:12.1.0-base-ubuntu22.04 nvidia-smi

实际项目里我用 vLLM 跑过 Qwen3-embedding 这类模型。vLLM 镜像本身就带齐全套依赖,只需要把模型目录挂载进去:

docker run --gpus all \ -p 8000:8000 \ --ipc=host \ --shm-size=16g \ -v /data/models:/models \ vllm/vllm-openai:latest \ --model /models/qwen3-embedding \ --task embed \ --port 8000

--ipc=host和--shm-size特别容易忘。PyTorch 和 vLLM 在数据处理时会大量使用共享内存,默认 64M 的/dev/shm会直接导致 “Bus error” 或者 CUDA 显存分配失败。先说清楚这个坑,后面你跑 AI 容器时能少走很多弯路。

4.2 ROS2 和 micro-ROS Agent 容器化

机器人操作系统 ROS2 是一套依赖极重的框架,版本之间互相不兼容。我身边做机器人开发的朋友,几乎人人都有被环境搞到重装系统的经历。ROS2 Humble 对应 Ubuntu 22.04,这个版本关系是硬约束,搞错了库文件全崩。

把 ROS2 环境容器化,等于给版本一致性上了保险。比如 micro-ROS Agent 是一个连接 ROS2 与嵌入式设备的关键桥接程序,直接在容器里跑:

docker run -it --rm --net=host \ microros/micro-ros-agent:humble \ udp4 --dev-fs /dev --port 8888

这里用--net=host不只是图省事,而是 micro-ROS 常需要使用多播和多端口通信,默认的 bridge 网络很容易把 UDP 包挡在外面。机器人通过串口或 UDP 接入时,如果发现 Agent 收不到数据,第一步就该检查网络模式是否改成了 host。

这类工具链用容器跑还有个额外好处,就是微控制器固件和上位机代码可以分开管理。上位机环境出了问题,删容器重建,三分钟恢复,不用再面对“装了一下午环境,最后发现版本不匹配”的绝望。

4.3 N100 小主机跑 20 个容器,靠资源规划而不是硬件堆料

Intel N100 这两年很火,低功耗、体积小,很多人拿它当家庭服务器,动不动就往上面塞二十个容器。我实测下来,N100 不是不能跑,而是不能无脑跑。

它通常配 16G 或 32G 内存,CPU 性能比主流服务器弱不少。如果每个容器都不做资源限制,二十个服务同时启动,内存马上爆掉,然后系统开始疯狂使用 swap,整个机器卡到没法操作。

我给这类小主机定了一套规则。第一,每个容器都设置资源上限:

services: app: image: your-app deploy: resources: limits: cpus: "0.5" memory: 256M

第二,日志必须轮转,否则默认的 JSON 日志文件会悄悄把磁盘塞满:

logging: driver: json-file options: max-size: "10m" max-file: "3"

第三,定期清理无用镜像和构建缓存:

docker system prune -af

按照这套思路,N100 跑二十个轻量服务完全可行。它的意义不在于性能,而在于把闲置算力充分利用起来,这也跟云原生“按需分配、高效利用资源”的理念正好吻合。

5. 高频问题排查速查与实战心得

5.1 常见报错对症下药

我把这些年处理过的高频问题整理成一个速查表,方便直接对照排查。

现象常见原因解决思路
Docker Desktop 启动报虚拟化不被检测BIOS 没开虚拟化,Windows 功能未启用开启 VT,勾选虚拟机平台和 WSL 支持,重启
docker 命令提示 Cannot connect to the Docker daemon服务没启动或启动失败systemctl status docker 查状态,journalctl 看日志
permission denied 访问 Docker 接口用户不在 docker 组usermod -aG docker $USER,重新登录
容器启动后宿主机访问不了端口端口映射缺失或防火墙拦截检查 -p 参数、防火墙规则、容器进程是否监听
镜像下载一直超时默认仓库访问不稳定配置 registry-mirrors 镜像加速地址
容器内网络 ping 不通外网DNS 配置不对,或默认 bridge 网关问题检查 /etc/resolv.conf,改用 --network=host 测试
磁盘突然爆满日志文件不轮转、镜像缓存堆积配置日志 max-size,定期 docker system prune
容器删了数据就没了没有挂载数据卷所有有状态服务必须使用 -v 或 volumes 配置

以上每一行我都实际遇到过,解决方案也是操作后验证过的。

5.2 容器网络“写不通”的排查路径

容器网络问题在我遇到的求助里占比极高,而且大多数是同一个原因:分不清不同网络模式下的通信方式。

Docker 默认创建 bridge 网络时,容器之间通过 IP 通信,但每次重建容器 IP 都会变,手动写死 IP 是非常危险的做法。正确逻辑是:同一 Compose 网络下的服务用服务名通信,跨主机的服务用映射端口通信。

排查命令是基本功:

# 查看容器实际 IP docker inspect container_name | grep IPAddress # 进入容器测试连通性 docker exec -it app curl http://mysql:3306 # 查看端口映射是否正确 docker port app

如果测试发现应用容器访问不了数据库容器,最可能就是两个容器不在同一个自定义网络里。解决方法:

docker network create app-net docker network connect app-net mysql8 docker network connect app-net app

不是网络协议高深,而是容器在共享内核的同时默认网络是隔离的,凡是“写在别的容器里老访问不通”的报错,百分之八十都是因为这个。

5.3 镜像下载慢还有没有别的解法

除了配置镜像加速,还有两个实际可用的思路我经常推荐。

第一,多台机器间迁移镜像用 save 和 load。比如在一台网络好的机器上把镜像拉下来,产出一个 tar 包:

docker save mysql:8.0.36 -o mysql.tar

拷贝到目标机器后:

docker load -i mysql.tar

这种方式对离线环境非常有效,比在每台机器上重复拉镜像快得多。

第二,注意镜像 tag 大小差异。有些官方镜像的版本 tag 之间存在巨大体积差距,多给个小版本就能避开某个大体积 tag。这个没什么技巧,多留意 Docker Hub 上的镜像详情页就行。

5.4 数据卷和文件权限的两个隐形坑

数据卷解决了持久化问题,但引入了两个副作用,第一个是文件属主变化。容器内进程默认以 root 运行,在挂载目录里生成的文件,宿主机上看到的所有者也是 root,普通用户删不掉也改不了。解决办法是在 docker run 时指定用户:

docker run -d --user 1000:1000 -v /data:/data your-image

或者构建镜像时直接创建非 root 用户,这是生产镜像的推荐做法。

第二个是 Windows 环境下挂载本地目录的 I/O 性能问题。Windows 文件系统和 WSL2 之间的转换有额外开销,如果容器大量读写本地挂载目录,速度会明显下降。我的建议是,开发时用绑定挂载方便改代码,但数据库、日志这类高频 IO 使用 Docker 管理的具名卷,性能会好很多。

最后再分享一个小习惯

写到这里,我想起一个自己坚持了几年的做法:每新增一个项目,都建一个独立目录,里面放docker-compose.yml、conf、data(别提交到 git)和.env环境变量文件。启动服务只执行docker compose up -d,清理服务只执行docker compose down。这个习惯帮我极大减少了“环境又坏了”的困扰,也让新同事接手项目时不用问“怎么跑起来”。

Docker 本身不解决所有问题,但它是接触云原生最合适的入口。当你把镜像、容器、数据卷、网络这套基本功练透,再去看 Kubernetes 或者其他编排平台,会发现很多概念都是相通的。容器技术的魅力,正在于它把复杂的工程问题,拆成了一个个可以重复、可验证的小步骤。希望这篇实战笔记能让你少踩几个我曾经踩过的坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询