Open wmenjoy opened 4 years ago
①添加并配置 /etc/docker/daemon.json 文件
②修改systemd管理的docker服务文件 /usr/lib/systemd/system/docker.service
docker image build -t bulletinboard:1.0 .
可以根据当前生成的image启动你的容器
# --publish -p 使用主机端口和docker进行固定映射,从而能够使外部服务访问docker 提供的功能
# --detach 以后台进程运行docker
# --name 给docker container起一个别名,后续会用到他
docker container run --publish 8000:8080 --detach --name bb bulletinboard:1.0
可以通过访问端口,来判断服务是否进行成功,也可以
docker exec -it bulletinboard:1.0 /bin/bash
进入docker, 进行服务日志的查看 当容器已经测试正常,可恶意删除别名
docker container rm --force bb
docker 不会使用独立的namespace 而是使用主机的ip和端口
指定新创建的容器和已经存在的一个容器共享一个Network Namespace
在这种模式下,Docker容器拥有自己的Network Namespace,但是,并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。
bridge模式是Docker默认的网络设置,此模式会为每一个容器分配Network Namespace、设置IP等,并将一个主机上的Docker容器连接到一个虚拟网桥上。
Docker可以从Dockerfile中读取指令来自动构建镜像。Dockerfile是一个文本文件,它包含了用户可以在命令调用以制作镜像的命令。用户可以使用docker build连续执行一些命令行指令来开启一个自动构建。
提示很明显,执行
docker login
docker login -u xxx
docker login -u xxx host:port #私有Docker Registries
sudo chmod 666 /var/run/docker.sock
docker history : 查看指定镜像的创建历史。
docker history [OPTIONS] IMAGE
如查看本地镜像runoob/ubuntu:v3的历史
root@runoob:~# docker history runoob/ubuntu:v3
IMAGE CREATED CREATED BY SIZE COMMENT
4e3b13c8a266 3 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0 B
<missing> 3 months ago /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$/ 1.863 kB
<missing> 3 months ago /bin/sh -c set -xe && echo '#!/bin/sh' > /u 701 B
<missing> 3 months ago /bin/sh -c #(nop) ADD file:43cb048516c6b80f22 136.3 MB
2017年的时候,Red Hat公司给出的一个对比
Image Type | Red Hat Enterprise Linux 7 Standard Image | Red Hat Enterprise Linux Atomic Image | Fedora | Fedora Modular | CentOS | Debian Stable | Ubuntu LTS | Alpine |
---|---|---|---|---|---|---|---|---|
Architecture | ||||||||
C Library | glibc | glibc | glibc | glibc | glibc | glibc | glibc | musl c |
Packaging Format | rpm | rpm | rpm | rpm | rpm | dpkg | dpkg | apk |
Core Utilities | GNU Core Utils | GNU Core Utils | GNU Core Utils | GNU Core Utils | GNU Core Utils | GNU Core Utils | GNU Core Utils | Busybox |
Size Across Wire | 73MB | 30MB | 75MB | 33MB | 72MB | 45MB | 47MB | 2MB |
Size on Disk | 200MB | 78MB | 230MB | 85MB | 192MB | 100MB | 120MB | 4MB |
Life Cycle | 10 years | 6 months | 6 months | 6 months | variable | 2 years | 5 years | unknown |
Compatibility Guarantees | Based on Tier | Generally within minor version | Generally, within a major version | Generally, within minor version | Follows RHEL | Generally within minor version | Generally within minor version | Unknown |
Troubleshooting Tools | Integrated with Technical Support | Integrated with Technical Support | Tools Container | Tools Container | Tools Container | Standard Packages | Standard Packages | Standard Packages |
Technical Support | Commercial & Community | Commercial & Community | Community | Community | Community | Community | Commercial & Community | Community |
ISV Support | Large Commercial l | LargeCommercial | Large Community | Community | Community | Community | Large Community | Small Community |
Security | ||||||||
Updates | Commercial | Commercial | Community | Community | Community | Community | Community | Community |
Tracking | OVAL Data, CVE Database, Vulnerability API & Errata, Lab Tools | OVAL Data, CVE Database, Vulnerability API & Errata, Lab Tools | Errata | Errata | Announce List, Errata | OVAL Data, CVE Database, & Errata | OVAL Data, CVE Database, & Errata | Limited Database |
Proactive Security Response Team | Commercial & Community | Commercial & Community | Community | Community | None | Community | Commercial & Community | None |
Performance | ||||||||
Automated Testing | Commercial | Commercial | None | None | None | None Found | None Found | None Found |
Proactive Performance Engineering Team | Commercial | Commercial | Community | Community | Community | Community | Community | Community |
1、 系统镜像大小并不是决定因素, 因为加入引用中间件后,最终大小并没有太大区别 2、优先考虑稳定性 ,安全性,以及当前公司的基础架构带来的维护成本和兼容性成本
Clear Linux 赢得了47% 的选票,而平分秋色的第二名分别是 Ubuntu 18.04、 Debian 9和 Arch Linux。 赢得10% 以下的是 Fedora 28,Alpine Linux 3.8,和 CentOS 7。 毫不奇怪,最慢的是 CentOS 7,因为默认情况下它的包比较老,但是已经证明 / 成熟了。
参考Using Nexus 3 as Your Repository – Part 3: Docker Images
docker 安装
docker volume create --name nexus-data
docker run -d --name nexus --restart=always -p 5000:5000 -p 8081:8081 -v nexus-data:/nexus-data sonatype/nexus3
参考harbor手册
{
"insecure-registries": [
"your-repo:8082",
"your-repo:8083"
]
}
{
"insecure-registries": [
"your-repo:8082"
]
}
## 如果配置不正确,是不生效的,请检查日志,查看是否正确
sudo kill -SIGHUP $(pidof dockerd)
sudo systemctl reload docker
## 镜像同步
1. [同步外网到本地](https://github.com/AliyunContainerService/sync-repo)
2. [不同docker registy之间的同步](https://github.com/AliyunContainerService/image-syncer)
3. [Harbor doesn't support pull cache as a mirror registry](https://github.com/goharbor/harbor/issues/120)
## 参考
4. [看Nexus3能代替Harbor 哪些的工作](https://www.2[]cto.com/net/201804/739065.html)
5. [kubernetes搭建Harbor无坑及Harbor仓库同步](https://www.cnblogs.com/keep-live/p/11395973.html)
6. [使用harbor和nexus作为docker registry](https://blog.csdn.net/weixin_34088838/article/details/91378874)
7. [Using Nexus 3 as Your Repository – Part 3: Docker Images](https://blog.sonatype.com/using-nexus-3-as-your-repository-part-3-docker-images)
8. [nexus 3: docker registy](https://help.sonatype.com/repomanager3/formats/docker-registry)
9. [Docker搭建Nexus私有仓库](https://www.jianshu.com/p/ecc4d430f992)
10. [Docker Mirror](https://github.com/docker/docker.github.io/blob/master/registry/recipes/mirror.md)
11. [Docker之Harbor私服的搭建及使用](https://blog.csdn.net/weixin_42082634/article/details/82850298)
12. [k8s集成habor](https://www.jianshu.com/p/b95e38a3471e)
13. [harbor手册](https://goharbor.io/docs/)
14. [Registry as a pull through cache](https://docs.docker.com/registry/recipes/mirror/)
15. [Docker 修改镜像源地址](https://blog.csdn.net/yhjay88/article/details/73790487)
修改/etc/docker/daemon.json,添加log-level为debug, info, warn, error, fatal.默认是info。 执行
$ sudo kill -SIGHUP $(pidof dockerd)
Docker镜像由很多镜像层(Layers)组成(最多127层),镜像层依赖于一系列的底层技术,比如文件系统(filesystems)、写时复制(copy-on-write)、联合挂载(union mounts)等技术. 详情参考UnionFS, 但是总得来说,Dockerfile中的每条指令都会创建一个镜像层,继而会增加整体镜像的尺寸。这样很容易我们的docker镜像就变的相当庞大
$ docker run -d test/test:0.2
$ docker export 747dc0e72d13 | docker import - test/test:0.3
基本制作步骤:
docker image list|grep helloworld|awk '{print $1":"$2}'|sed 's/^/docker image rm /g;' |sh
#拉取镜像
docker run golang go get -v github.com/golang/example/hello/...
# 编译go代码
docker run -e GOPROXY=$proxyHost--rm -v"$PWD":/test -w /test golang go build .
# 以指定版本的go编译
docker run -e GOPROXY=$proxyHost--rm -v"$PWD":/test -w /test golang:v1.3.0 go build .
# 一次性使用容器(--rm)并且执行
docker run --rm golang sh -c \
"go get github.com/golang/example/hello/... && exec hello"
直接安装到$PATH docker run -v /usr/local/bin:/go/bin \ golang get github.com/golang/example/hello/...
docker run -e GOOS=darwin -e GOARCH=amd64 -v /tmp/crosstest:/go/bin \ golang go get github.com/golang/example/hello/...
#### 问题汇总
1、如何不依赖于libc
要使用它,只需在go get选项加入-tags netgo -installsuffix netgo。
* tags netgo指示工具链使用netgo。
* installsuffix netgo确保结果库(任何)被一个不同的,非默认的目录所替代。如果做多重go get(或go build)调用,这将避免代码创建和用不用netgo之间的冲突。如果像目前我们讲到的这样,在容器里创建,是完全没有必要的。因为这个容器里面永远没有其他Go代码要编译。但它是个好主意,习惯它,或至少知道这个标识存在。
####
## 参考
1.[docker与go的巧妙结合](http://dockone.io/article/1712)
2.[go交叉编译](https://dave.cheney.net/2015/08/22/cross-compilation-with-go-1-5)
Docker Machine is a tool that lets you install Docker Engine on virtual hosts, and manage the hosts with docker-machine commands. You can use Machine to create Docker hosts on your local Mac or Windows box, on your company network, in your data center, or on cloud providers like Azure, AWS, or DigitalOcean.
# 列出
docker images
# 导出
docker save eb40dcf64078> /root/mydjango-save-1016.tar
## 导入
docker load < /root/mydjango-save-1016.tar
docker tag eb40dcf64078 django:latest
docker 镜像导入导出有两种方法:
一种是使用 save 和 load 命令
使用例子如下:
docker save ubuntu:load>/root/ubuntu.tar
docker load<ubuntu.tar
一种是使用 export 和 import 命令
使用例子如下:
docker export 98ca36> ubuntu.tar
cat ubuntu.tar | sudo docker import - ubuntu:import
需要注意两种方法不可混用。
操作系统修改时区
# 1、CentOS6、Ubuntu16
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
# 2、CentOS7、RHEL7、Scientific Linux 7、Oracle Linux 7 最好的方法是使用timedatectl命令
timedatectl list-timezones |grep Shanghai #查找中国时区的完整名称
Asia/Shanghai
timedatectl set-timezone Asia/Shanghai #其他时区以此类推
# 3、直接手动创建软链接
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
如果不生效,还需要安装tzdate
对于docker镜像,可以通过三种防水扩展
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
自定义docker镜像,替换原始镜像
FROM xxxx
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt-get update
RUN apt-get install tzdata
RUN dpkg-reconfigure --frontend noninteractive tzdata
通过挂在本地服务来实现
apiVersion: v1
kind: Pod
metadata:
name: james-java
namespace: james
labels:
name: james-java
spec:
containers:
- name: james-java
image: 192.168.0.252/szlaozi/java:v0.0.1
volumeMounts:
- name: tz-config
mountPath: /etc/localtime
ports:
- containerPort: 7102
volumes:
- name: tz-config
hostPath:
path: /etc/localtime
1、 根据创建时间来清理 2、根据是否使用的时间 3、每一个docker image 保留固定的版本(不会误器清理)
wget https://s3.eu-west-2.amazonaws.com/nexus-cli/1.0.0-beta/linux/nexus-cli
[root@fs02-192-168-126-16 ~]# ./nexus-cli configure
Enter Nexus Host: http://192.168.xxx.xxx:xxx
Enter Nexus Repository Name: docker-private
Enter Nexus Username: xxxx
Enter Nexus Password: xxxx
keepDate=3
./nexus-cli image ls|grep -v Total|sed "s#^#\./nexus-cli image delete -keep $keepDate -name #"|sh
注: 前三步,可以写成一个定时任务
nexus3 磁盘空间满后 docker 启动报"cannot open local storage '/nexus-data/db/config' with mode=rw"
java.lang.NullPointerException: null
at com.orientechnologies.orient.core.storage.impl.local.paginated.wal.ODiskWriteAheadLog.cutTill(ODiskWriteAheadLog.java:919)
at com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage.makeFullCheckpoint(OAbstractPaginatedStorage.java:3706)
at com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage.doClose(OAbstractPaginatedStorage.java:4413)
at com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage.close(OAbstractPaginatedStorage.java:581)
at com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage.open(OAbstractPaginatedStorage.java:316)
at com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx.open(ODatabaseDocumentTx.java:259)
at org.sonatype.nexus.orient.DatabaseManagerSupport.connect(DatabaseManagerSupport.java:178)
at org.sonatype.nexus.orient.DatabaseManagerSupport.createInstance(DatabaseManagerSupport.java:312)
at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)
at org.sonatype.nexus.orient.DatabaseManagerSupport.instance(DatabaseManagerSupport.java:289)
at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:290)
at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
2020-11-19 01:57:31,172+0000 ERROR [ForkJoinPool.commonPool-worker-1] *SYSTEM com.orientechnologies.orient.core.storage.impl.local.paginated.OLocalPaginatedStorage - Exception `47820D6D` in storage `plocal:/nexus-data/db/config`: 2.2.36 (build d3beb772c02098ceaea89779a7afd4b7305d3788, branch 2.2.x)
com.orientechnologies.orient.core.exception.OStorageException: Cannot open local storage '/nexus-data/db/config' with mode=rw^M
DB name="config"
at com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage.open(OAbstractPaginatedStorage.java:323)
at com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx.open(ODatabaseDocumentTx.java:259)
at org.sonatype.nexus.orient.DatabaseManagerSupport.connect(DatabaseManagerSupport.java:178)
at org.sonatype.nexus.orient.DatabaseManagerSupport.createInstance(DatabaseManagerSupport.java:312)
at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)
at org.sonatype.nexus.orient.DatabaseManagerSupport.instance(DatabaseManagerSupport.java:289)
at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:290)
at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: java.lang.NullPointerException: null
at com.orientechnologies.orient.core.storage.impl.local.paginated.wal.ODiskWriteAheadLog.cutTill(ODiskWriteAheadLog.java:919)
at com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage.makeFullCheckpoint(OAbstractPaginatedStorage.java:3706)
at com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage.recoverIfNeeded(OAbstractPaginatedStorage.java:3937)
at com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage.open(OAbstractPaginatedStorage.java:288)
... 14 common frames omitted
刚开始以为整的是磁盘满导致的,清理重启,发现不行,后搜寻发现,是磁盘满了导致索引损坏导致的。开始着手修复db
docker run --rm -v /home/jenkins/nexus-data:/nexus-data -it sonatype/nexus3:3.29.2 bash
java -jar ./lib/support/nexus-orient-console.jar
CONNECT plocal:/nexus-data/db/component admin admin
REBUILD INDEX *
REPAIR DATABASE --fix-graph
REPAIR DATABASE --fix-links
REPAIR DATABASE --fix-ridbags
REPAIR DATABASE --fix-bonsai
DISCONNECT
EXIT
docker-compose stop
docker-compose up -d
Docker 概念
Docker 是什么?
正如Docker官网所言:
Docker 简单来讲是一个基于容器的生态开源平台,它不仅仅是容器,而且集成了软件的生命周期的管理, 利用Docker平台集成的应用程序的开发,发布,运行管理的功能和容器的优势,你可以很好的将软件和基础设施隔离,实现软件的快速交付。同时容易是轻量级的,具有很强的隔离性和安全性,这样可以提供给你一种成本更低的软件开发方案。
Docker Engine
Docker 技术的核心便是Docker Engine, 他是一个CS架构的应用程序,主要有三个组件
为什么选择Docker
Docker 本身灵活,高效可移植,松耦合,可扩展,安全;
Docker可以用来做什么?
应用程序快速一致性的交付
Docker 的基于容器的平台支持高度可移植的工作负载。 Docker 容器可以在开发人员的本地笔记本电脑上运行,也可以在数据中心的物理或虚拟机、云提供商或混合环境中运行。
Docker使用容器来标准化应用程序和服务的环境,能够极大简化软件的开发周期,避免配置异常的问题,对CI/CD非常有用:
响应式的部署和扩展
Docker 的可移植性和轻量级特性使得动态管理work Loads变得非常容易,可以根据业务需求以接近实时的方式扩展或拆卸应用程序和服务。
相同硬件上可以部署更多的应用
Docker是轻量级和快速的。 它为基于虚拟机监控程序的虚拟机提供了一个可行的、经济有效的替代方案,因此您可以使用更多的计算能力来实现业务目标。 Docker 非常适合高密度环境和中小型部署,在这些环境中您需要用更少的资源完成更多的工作。
容器和虚拟机的对比
容器在 Linux 上本地运行,并与其他容器共享主机的内核。 它运行一个离散的进程,占用的内存不比其他任何可执行文件多,因此它是轻量级的。
相比之下,虚拟机(VM)要完全运行一个操作系统,通过管理程序虚拟访问主机资源。 一般来说,除了您的应用程序逻辑所消耗的内容之外,vm 还会产生大量的开销。
Docker 的架构
如上图所示,Docker使用CS体系结构,Client通过和Docker的Daemon来完成Docker容器的构建、发布、运行工作,Client和Daemon可以在同一台主机,也可以在不同主机上(通过网络进行交互)
Docker Daemon
Docker Daemon(dockerd)监听 通过 Docker API 过来的请求,并管理 Docker 对象,如图像、容器、网络和卷。 守护进程还可以与其他守护进程通信,用来管理 Docker 服务。
Docker Client
Docker Client(Docker命令)是许多 Docker 用户与 Docker 交互的主要方式。 当使用诸如 docker run 之类的命令时,客户机将这些命令发送给 dockerd,然后又dockerd执行这些命令。 Docker 命令使用 Docker API和dockerd进行通信。 Docker 客户机可以与多个守护进程通信。
Docker Registries
Docker Registries 用来存储Docker Image, Docker Hub是一个公共Docker Regstires, 类似于公用的maven仓库,是Docker默认配置的。 你也可以注册自己的私有Regsitries
当您使用 docker pull 或 docker 运行命令时,所需的映像将从您配置的Regsitries中提取出来。 当您使用 docker push 命令时,您的映像将被推送到您配置的Regsitries中。
Docker 对象
对象
这里所说的Docker对象指的是镜像(images), 容器(containers), 网络(networks), 卷(volume), 插件,和其他对象
镜像(Image)
镜像是一个创建Docker Container的只读指令模板,通常,一个镜像基于另一个镜像(dockerfile中农的FROM),并在基础上增加一些定制,比如在centos镜像的基础上,增加nginx服务器,以及运行nginx所需的配置
可以使用其他人在Docker Registries创建好的镜像,也可以创建自己的镜像,创建镜像,需要一个dockerfile.Dockerfile用间接的语法来描述创建和运行镜像所需要的步骤。Dockerfile 中的每条指令都在图像中创建一个图层。 当您更改 dockerfile 并重新生成图像时,只会重新生成已更改的图层。 与其他虚拟化技术相比,这是使映像如此轻量化、小巧和快速的部分原因。
容器(Container)
容器是镜像的可运行实例。可以使用Docker Rest API或者Docker CLI 来创建,启动,停止,移动,删除容器,也可以将容器连接成一个或多个网络,可以给镜像添加存储卷,生成可以基于当前的状态创建一个新的镜像 默认情况下,容器与其他容器及其主机相对隔离较好。 可以控制容器的网络、存储或其他基础子系统与其他容器或主机的隔离程度。
容器是由其镜像以及创建或启动时提供给它的任何配置选项定义的。 当移除容器时,对其状态的任何未存储在持久性存储中的更改都将消失。
服务(Service)
严格的将,这个Service 是对Docker 容器所运行服务的一个更上层抽象,借助于负载均衡,不同容器的管理机制,来提供一个整体的引用程序,比如K8s的服务概念,Docker Swarm的服务概念
底层的技术
基础技术
Docker 是用go编写,利用lInux提供的Namespace, Control group 和Union File System来实现功能
Namespace
Docker 使用Namespace技术提供称为容器的隔离工作区。 运行容器时,Docker 为该容器创建一组Namespace
这些Namespace提供了一个隔离层。 容器的每个方面都运行在一个单独的Namespace中,它的访问受到该Namespace的限制。
linux提供了7种Namespace,Docker目前使用了一下5种(User name space没有使用)
Control Groups
Linux 上的 Docker Engine 还依赖于另一种称为cgroups的技术。 Cgroup 将应用程序限制为一组特定的资源。 Control Groups允许 Docker 引擎向容器共享可用的硬件资源,并可选地实施限制和约束。 例如,可以将可用内存限制为特定容器。
Union file systems
nion 文件系统,或 UnionFS,是通过创建层来操作的文件系统,这使得它们非常轻量级和快速。 Docker Engine 使用 UnionFS 为容器提供构建块。 Docker Engine 可以使用多个 UnionFS 变体,包括 AUFS、 btrfs、 vfs 和 DeviceMapper。
容器格式 format
Docker Engine 将Namespace、Controle group和 UnionFS 组合成一个称为Container format 的包装器,默认的容器格式是 libcontainer。 在未来,Docker 可能会通过集成技术来支持其他容器格式,如 BSD Jails 或者 Solaris Zones.
参考