docker(四)--Dockerfile

 

鏡像製做
1,基於容器製做
2,dockerfilehtml


docker 語法格式mysql

  • # comment 註釋信息
  • INSTRUCTION arguments 指令與參數,一般指令都是大寫

docker是自上而下順序執行的,第一個非註釋行必須是'FROM'指令,基於哪一個基礎鏡像來作的linux

 

Dockerfile
1,須要有一個專用工做目錄
2,dockerfile放在這個工做目錄下,且文件名首字母大寫
3,dockerfile若是要打包其餘文件到鏡像,這些文件的路徑必須是當前工做目錄或下級子目錄,不能是父目錄
4,dockerfile支持製做一個隱藏文件.dockeringore,這個文件中能夠寫入文件路徑,在打包時,全部寫在.dockeringore中的文件都不包含進去。 例如要打包一個目錄下的文件,但不包含其中某3個文件,能夠將這3個文件的路徑寫入.dockeringore。那麼.dockeringore自己以及裏面包含的文件,都不會被打包進去。nginx

變量web

變量引用:$variable_name  ${variable_name}
${variable:-word}   若是變量沒設置或值爲空,就使用word字串,不然就返回變量值。用得較多sql

[root@abao ~]# echo ${NAME:-lily}
lily
[root@abao ~]# NAME=jerry
[root@abao ~]# echo ${NAME:-lily}
jerrydocker

${variable:+word} 變量有值就顯示word, 沒值就沒shell

[root@abao ~]# NAME=jerry
[root@abao ~]# echo ${NAME:+tom}
tom
[root@abao ~]# unset NAME
[root@abao ~]# echo ${NAME:+tom}windows

 

Dockerfile經常使用指令 

FROM  最重要的一個指令,且必須爲Dockerfile文件開篇的第一個非註釋行,用於指定基準鏡像,後續的指令運行於基準鏡像提供的運行環境。
基準鏡像能夠是任何可用鏡像文件,默認狀況下,docker build會在docker主機上查找指定的鏡像文件,當其不存在時,則從Docker Hub上拉取
語法:
- FROM <repository>[:<tag>]或
- FROM <repository>@<digest> # <repository>:指定做爲base image的名稱,digest指定hash碼數組

MAINTAINER (可選,已廢棄被LABLE代替)    用於讓Dockerfile製做者提供本人的信息

LABLE  爲鏡像指定各類元數據。   語法:LABEL <key>=<value> <key>=<value> <key>=<value> ... 鍵值數據

COPY   從宿主機把文件複製到目標鏡像中
語法:
- COPY <src> ..<des> 或
- COPY ["<src>",..."<dest>"]
<src>: 要複製的源文件或目錄,支持使用通配符(通常是當前工做目錄)
<dest>: 目標路徑,即正在建立的image的文件系統路徑,建議爲<dest>使用絕對路徑,不然以WORKDIR爲其起始路徑
注:在路徑中有空白字符時,一般使用第二種格式

文件複製準則
- <src> 必須是build上下文中的路徑,不能是其父目錄的文件
- <src> 若是是目錄,<src>目錄自身不會被複制,其內部文件或子目錄都會被複制。 (目錄自身不會被複制,如cp -r tmp/* /tmp)
- 若是指定了多個<src>,或在<src>中使用了通配符,則<dest>必須是一個目錄,且必須以/結尾,不加會報錯
- 若是<dest>事先不存在,它將會自動建立

COPY index.html /data/web/html/      # index.html必定要在當前目錄下,或在子目錄下
COPY yum.repos.d /etc/yum.repos.d/     #複製yum.repos.d目錄只是複製它下面的內容,因此要寫明目標目錄名

Dockerfile:

# Description: test image FROM busybox:latest #MAINTAINER "abao <abao@163.com>" LABEL maintainer="abao <abao@163.com>" COPY index.html /data/web/html/

# docker build -t testhttpd:v1.0 ./
# ls
Dockerfile index.html
# docker run --name tinyweb1 --rm tinyhttpd:v0.1 cat /data/web/html/index.html
<h1>hello, Busybox httpd server image.</h1>

 

ADD    相似於COPY指令,ADD支持使用tar文件和URL路徑
語法:
ADD <src> ...<dest> 或
ADD ["<src>",.."<dest>"]

- 同COPY指令
- <src>爲URL且<dest>不以/結尾,則<src>指定的文件將被下載並被建立爲<dest>;若是<dest>以/結尾,則URL指定的文件將被下載並保存到<dest>/目錄下
- <src>是一個本地壓縮格式tar文件,它將被展開爲一個目錄,相似於"tar -x"命令;  可是,經過URL獲取的tar文件不會自動展開 
- <src>有多個,或使用了通配符,則<dest>必須是一個以/結尾的目錄路徑,若是<dest>不以/結尾,則其被視做一個普通文件,<src>的內容被直接寫入到<dest>;

WORKDIR  用於爲Dockerfile中全部的RUN、CMD、ENTRYPOINT、COPY和ADD指定設定工做目錄

  語法: WORKDIR <dirpath>

 

volume  用於在image中建立一個掛載點目錄,用來掛載Docker host上的卷或其它容器上的卷
語法: VOLUME <mountpoint> 或 VOLUME |」<mountpoint>"
若是掛載點目錄路徑下此前在文件存在,docker run命令會在卷掛載完成後將此前全部文件複製到新掛載的卷中
卷有兩種格式:綁定掛載卷和docker管理卷,dockerfile中只能用docker管理的卷,即指定容器中的路徑,而不能指定宿主機的目錄

VOLUME /data/mysql/ # docker build -t tinyhttpd:v0.5 ./ # docker run --name tinyweb1 --rm tinyhttpd:v0.5 sleep 100 # docker inspect tinyweb1 "Mounts": [ { "Type": "volume", "Name": "a08c5c5c1c4779e77a97682cd6309fbc67e61ece448965354eea28556b7dae29", "Source": "/var/lib/docker/volumes/a08c5c5c1c4779e77a97682cd6309fbc67e61ece448965354eea28556b7dae29/_data", "Destination": "/data/mysql",

  

EXPOSE  待暴露端口,用於爲容器打開指定要監聽的端口以實現與外部通訊 

(只能指定容器暴露的端口,再隨機綁定宿主機的端口,不能指定宿主機綁定的端口)
語法: 
    EXPOSE <port>[/<protocol>][<port>[/<protocol>]...]
         <protocol> 用於指定傳輸層協議,可爲tcp或udp兩者之一,默認爲TCP協議
         EXPOSE指令可一次指定多個端口,例如EXPOSE 112/udp 112/tcp

 注意:寫在dockerfile中的端口暴露並不會直接暴露,使用docker run中的-P選項時,不用指定,會自動讀取鏡像中要暴露那個端口

EXPOSE 80/tcp # docker build -t tinyhttpd:v0.6 ./ # docker run --name tinyweb1 --rm tinyhttpd:v0.6 /bin/httpd -f -h /data/web/html # docker inspect tinyweb1 查看容器的ip # curl 172.17.0.3
<h1>hello, Busybox httpd server image.</h1> # docker port tinyweb1 #實際查詢沒有暴露端口 # docker kill tinyweb1 tinyweb1 # docker run --name tinyweb1 --rm -P tinyhttpd:v0.6 /bin/httpd -f -h /data/web/html # docker port tinyweb1 80/tcp -> 0.0.0.0:32769 能夠在外部訪問宿主機:32769

 

ENV  用於爲鏡像定義所須要的環境變量,並可被Dockerfile文件中位於其後的其它指令(如ENV、ADD、COPY等)所調用,調用格式爲$variable_name}或$variable_name

語法:
ENV <key> <value>    #一次只能設置一個變量,<key>以後全部內容均會被視做其<value>的組成部分
ENV <key>=<value>...   # 一次可設置多個變量,每一個變量爲一個"<k>=<v>"的鍵值對,若是<value>中有空格,能夠用反斜線\轉義,也可用<value>引號進行標識;另外,反斜線也可用於續行。當定義多個變量時,建議使用這種方式,以便在同一層中完成全部功能

例如:

ENV DOC_ROOT=/data/web/html/ \ WEB_SERVER_PACKAGE="nginx-1.17.8" COPY index.html ${DOC_ROOT:-/data/web/html/} WORKDIR /usr/local/src/ ADD ${WEB_SERVER_PACKAGE}.tar.gz ./ # docker build -t tinyhttpd:v0.7 ./ # docker run --name tinyweb1 --rm tinyhttpd:v0.7 ls /usr/local/src/ nginx-1.17.8 # docker run --name tinyweb1 --rm tinyhttpd:v0.7 cat /data/web/html/index.html <h1>hello, Busybox httpd server image.</h1> # docker run --name tinyweb1 --rm tinyhttpd:v0.7 printenv PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin HOSTNAME=1c99b2e89674 DOC_ROOT=/data/web/html/ WEB_SERVER_PACKAGE=nginx-1.17.8 HOME=/root

 在docker run時 經過-e 選項 也可指定變量

# docker run --name tinyweb1 --rm -e WEB_SERVER_PACKAGE=nginx-1.14.0 tinyhttpd:v0.7 printenv PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin HOSTNAME=e13da61e21b2 WEB_SERVER_PACKAGE=nginx-1.14.0 DOC_ROOT=/data/web/html/ HOME=/root # docker run --name tinyweb1 --rm -e WEB_SERVER_PACKAGE=nginx-1.14.0 tinyhttpd:v0.7 ls /usr/local/src/ nginx-1.17.8 #能夠看到docker run時能夠修改環境變量的值,可是並不會改變docker build過程

 

 RUN    用於 docker build 過程當中運行的命令

ENV DOC_ROOT=/data/web/html/ \ WEB_SERVER_PACKAGE="nginx-1.17.8.tar.gz" ADD http://nginx.org/download/${WEB_SERVER_PACKAGE} /usr/local/src/
WORKDIR /usr/local/src/ RUN cd /usr/local/src && \ tar -xf ${WEB_SERVER_PACKAGE} && \ mv nginx-1.17.8 webserver # docker build -t tinyhttpd:v0.8 ./ # docker run --name tinyweb1 --rm -it tinyhttpd:v0.8
/usr/local/src # ls nginx-1.17.8.tar.gz  webserver

Syntax:

  • RUN <command> 或
  • RUN ["<executable>","<param1>","<param2>"]    #注意要使用雙引號,單引號可能會出錯

- 第一種格式,<command>一般是一個shell命令,且以"/bin/sh -c"來運行它,看成shell子進程來運行,這意味此進程在容器的PID不爲1,不能接收Unix信號,所以,當使用docker stop <container>命令中止容器時,此進程接收不到SIGTERM信號。

- 第二種中的參數是一個JSON格式的數組,其中<executable>爲要運行命令,後面<paramN>爲傳遞給命令的選項或參數;然而,命令不會以"/bin/sh -c"來發起,也就是直接由內核來建立,所以常見的shell操做如變量替換以及通配符(?,*等)替換將不會進行; 不過,若是想使用shell來操做,可使用如下格式:RUN ["/bin/bash","-c","<exectuable>","<param1>"]

 

CMD   用於定義把鏡像啓動爲容器時(docker run) 默認運行的命令

 

-  相似於RUN指令,CMD指令也可用於運行任何命令或應用程序,不過,兩者的運行時間點不一樣,RUN指令運行於映像文件構建過程當中,而CMD指令運行基於Dockerfile構建出新映像文件 啓動一個容器時

-  CMD指令的首要目的在於爲啓動容器指定默認要運行的程序,且其運行結束後,容器也將終止;不過,CMD指定的命令其能夠被docker run的命令選項所覆蓋
- 在Dockerfile中能夠存在多個CMD指令,但僅最後一個會生效
語法:

  • CMD <command> #  自動運行爲shell子進程,最大的壞處是id不爲1,沒法使用docker stop去中止,由於它接收不到信號,接信號都是進程號爲1的進程,由於它是一個超管進程,可讓它爲1
  • CMD ["<executable>","<param1>","<param2>"]     #  直接啓動爲用戶id爲1的進程,能夠接收處理shell的信號
  • CMD ["<param1>","<param2>"]   # 用於 ENTRYPOINT 指令提供默認參數

測試第一種方法:

FROM busybox LABEL maintainer="abao <abao@163.com>" app="httpd" ENV WEB_DOCROOT="/data/web/html/" RUN mkdir -p ${WEB_DOCROOT} && \ echo "<h1> Busybox httpd server.</h1>" > ${WEB_DOCROOT}/index.html CMD /bin/httpd -f -h ${WEB_DOCROOT} #啓動容器時,應該是做爲shell的子進程來運行,pid不爲1

#建立鏡像
 docker build -t tinyhttpd:v1.0 ./
# 查看鏡像 
docker image inspect tinyhttpd:v1.0 "Cmd": [ "/bin/sh",  #能夠看到默認會以"/bin/sh -c"來運行它 "-c", "/bin/httpd -f -h ${WEB_DOCROOT}" ], # 啓動容器
  docker run --name tinyweb1 -it --rm -P tinyhttpd:v1.0 沒有進入交互式,# 鏡像中定義默認運行的程序是httpd,是shell的子進程,不是shell,沒有交互式接口 # 使用exec進入交互式
]# docker exec -it tinyweb1 /bin/sh
/ # ps
PID USER TIME COMMAND 1 root      0:00 /bin/httpd -f -h /data/web/html/  # 這裏id爲1是由於默認執行了exec的替換,爲了確保容器能自動接收unix的信號,執行docker stop時能中止,執行docker kill時能殺掉
    6 root      0:00 /bin/sh 11 root      0:00 ps / # printenv
HOSTNAME=5b27cc8a100f SHLVL=1 HOME=/root TERM=xterm PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin WEB_DOCROOT=/data/web/html/ PWD=/
/ #

 

第二種方式:

CMD ["/bin/httpd","-f","-h ${WEB_DOCROOT}"] #製做鏡像
docker build -t tinyhttpd:v1.1 ./
# 查看鏡像
docker image inspect tinyhttpd:v1.1
            "Cmd": [ "/bin/httpd", "-f", "-h ${WEB_DOCROOT}" ], # 啓動容器
 docker run --name tinyweb1 -it --rm -P  tinyhttpd:v1.1 httpd: can't change directory to ' ${WEB_DOCROOT}': No such file or directory
#會報錯,由於程序不會運行爲shell子進程,而變量是爲shell變量,因此沒法識別

 

ENTRYPOINT

# docker run --name tinyweb2 -it --rm -P tinyhttpd:v1.2 ls /data/web/html index.html # docker run --name tinyweb2 -it --rm -P tinyhttpd:v1.2 httpd -f -h /data/web/html/
# 在dockerfile中指定的默認要運行的程序是httpd,可是docker run時能夠本身指定命令(ls /data/web/html和httpd ...等,覆蓋鏡像中默認要運行的命令

 

- ENTRYPOINT能夠實現相似於CMD指令的功能,用於爲容器指定默認運行程序,從而使得容器像是一個單獨的可執行程序
- 與CMD不一樣的是,由ENTRYPOINT啓動的程序不會被docker run命令行指定的參數所覆蓋,並且,這些命令行參數會被當參數傳遞給ENTRYPOINT指定的程序
- 不過docker run命令中的 --entrypoint選項的參數 可覆蓋ENTRYPOINT指令指定的程序

Syntax:

- ENTRYPOINT <command>
- ENTRYPOINT ["<executable","<param1>","<param2>"]

   docker run 命令傳入的命令參數會覆蓋CMD指令的內容而且附加到ENTRYPOINT命令最後作爲其參數使用
   Dockerfile文件中也能夠存在多個ENTRYPOINT指令,但僅有最後一個會生效

FROM busybox LABEL maintainer="abao <abao@163.com>" app="httpd" ENV WEB_DOCROOT="/data/web/html/" RUN mkdir -p ${WEB_DOCROOT} && \ echo "<h1> Busybox httpd server.</h1>" > ${WEB_DOCROOT}/index.html ENTRYPOINT /bin/httpd -f -h ${WEB_DOCROOT} 構建鏡像並啓動容器,測試: docker run --name tinyweb2 -it --rm -P tinyhttpd:v1.3 ls /data/web/html #並無執行ls,而是執行的/bin/httpd -f -h /data/web/html ls /data/web/html。 把ls...看成參數附加在命令後面

 

CMD的第三種方式:

同時有 CMD 和 ENTRYPOINT 時,CMD指定的選項會用於爲ENTRYPOINT提供默認參數

FROM busybox LABEL maintainer="abao <abao@163.com>" app="httpd" ENV WEB_DOCROOT="/data/web/html/" RUN mkdir -p ${WEB_DOCROOT} && \ echo "<h1> Busybox httpd server.</h1>" > ${WEB_DOCROOT}/index.html CMD ["/bin/httpd","-f","-h ${WEB_DOCROOT}"] ENTRYPOINT ["/bin/sh","-c"] #默認會執行 /bin/sh -c /bin/httpd -f -h /data/web/html
docker build -t tinyhttpd:v1.3 ./ docker run --name tinyweb2 -it --rm -P tinyhttpd:v1.4 "ls /data/web/html" index.html #發現執行了 ls 命令,由於CMD的參數會被當成ENTRYPOINT的默認參數,而命令行參數會被當參數傳給ENTRYPOINT,因此CMD的參數被覆蓋了。執行的命令是 /bin/sh -c ls /data/web/html

 

 

配置nginx示例:

# cat Dockerfile 
FROM nginx:1.14-alpine LABEL maintainer="abao <abao@abao.163.com>" ENV NGX_DOC_ROOT="/data/web/html/" ADD index.html ${NGX_DOC_ROOT} ADD entrypoint.sh /bin/ CMD ["/usr/sbin/nginx","-g","daemon off;"] ENTRYPOINT ["/bin/entrypoint.sh"] # cat entrypoint.sh  #!/bin/sh
cat > /etc/nginx/conf.d/www.conf << EOF server { server_name ${HOSTNAME}; listen ${IP:-0.0.0.0}:${PORT:-80}; root ${NGX_DOC_ROOT:-/usr/share/nginx/html}; } EOF exec "$@" chmod +x entrypoint.sh echo " <h1> New doc root for nginx.</h1>" > index.html docker build -t myweb:v0.5 ./ docker run --name myweb1 --rm -P  myweb:v0.5 docker exec -it myweb1 /bin/sh / # ps -ef
PID USER TIME COMMAND 1 root      0:00 nginx: master process /usr/sbin/nginx -g daemon off; 8 nginx     0:00 nginx: worker process 9 root      0:00 /bin/sh 14 root      0:00 ps -ef / # cat /etc/nginx/conf.d/www.conf 
server { server_name 790db97efd5a; listen 0.0.0.0:80; root /data/web/html/; } / # wget -O - -q 790db97efd5a #訪問nginx
<h1> New doc root for nginx.</h1>
docker run時傳遞環境變量 docker run --name myweb1 --rm -P -e "PORT=8080" myweb:v0.5 docker exec -it myweb1 /bin/sh / # ps PID USER TIME COMMAND 1 root 0:00 nginx: master process /usr/sbin/nginx -g daemon off; #pid爲1,由於entrypoint.sh中執行命令是exec 8 nginx 0:00 nginx: worker process 9 root 0:00 /bin/sh 14 root 0:00 ps / # cat /etc/nginx/conf.d/www.conf server { server_name 0a9b161911b8; listen 0.0.0.0:8080; root /data/web/html/; } / # wget -O - -q 0a9b161911b8:8080 <h1> New doc root for nginx.</h1>

 一般狀況下,可使用 entrypoint腳本把須要修改的部分,如IP,PORT,做爲變量放在配置文件中,讓腳本能夠經過使用這些環境變量生成配置文件,在docker run時用環境變量給它傳值。

 

USER

用於指定運行image時的或運行Dockerfile中任何RUN、CMD或ENTRYPOINT指定的程序的用戶名或UID
默認狀況下,container的運行身份爲root用戶

語法:
    USER <UID>|<UserName>
     須要注意的是,<UID>能夠爲任意數字,可是實踐中其必須爲/etc/passwd中某用戶的有效UID,不然docker run命令將運行失敗

 

HEALTHCHECK
 不依據主進程運行與否來判斷健康與否,而是檢測是否真正能提供服務,使用wget或curl查看是否能加載頁面。

語法:

HEALTHCHECK [OPTIONS] CMD command   (經過運行一個command來檢查容器主進程是否健康)
HEALTHCHECK NONE      (拒絕任何健康狀態檢測,包括默認的檢測機制)

OPTIONS:
--interval=DURATION(default: 30s) #檢測間隔時間
--timeout=DURATION(default: 30s) #超時時間
--start-period=DURATION(default: 0s) #容器啓動時,可能進程服務還沒初始化完成,這時去檢查會是報錯失敗的,檢測能夠等待一段時間,像tomcat程序啓動時可能須要10s
--retries=N(default: 3) #重試次數

響應結果:
0:success
1: unhealthy
2: reserved 預留,自定義

示例:
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost || exit 1 

FROM nginx:1.14-alpine LABEL maintainer="abao <abao@abao.163.com>" ENV NGX_DOC_ROOT="/data/web/html/" ADD index.html ${NGX_DOC_ROOT} ADD entrypoint.sh /bin/ EXPOSE 80/tcp HEALTHCHECK --start-period=3s CMD wget -O - -q http://${IP:-0.0.0.0}:${PORT:-80}/ CMD ["/usr/sbin/nginx","-g","daemon off;"] ENTRYPOINT ["/bin/entrypoint.sh"] # docker run --name myweb1 --rm -P -e "PORT=8080" myweb:v0.6
127.0.0.1 - - [25/Feb/2020:03:16:53 +0000] "GET / HTTP/1.1" 200 34 "-" "Wget" "-"
127.0.0.1 - - [25/Feb/2020:03:17:23 +0000] "GET / HTTP/1.1" 200 34 "-" "Wget" "-"
127.0.0.1 - - [25/Feb/2020:03:17:53 +0000] "GET / HTTP/1.1" 200 34 "-" "Wget" "-"
127.0.0.1 - - [25/Feb/2020:03:18:24 +0000] "GET / HTTP/1.1" 200 34 "-" "Wget" "-"

 

SHELL    定義運行程序默認使用的shell

語法: SHELL ["executable","parameters"]
默認linux是["/bin/sh","-c"],windows是["cmd","/S","/C"]

STOPSIGNAL

進程號id 爲 1 的,能夠接收 docker stop 命令,從而可以中止主進程,從而中止容器。如 stop 發送的是 15,signal 是無符號整型數字(必須匹配 kernal 的 syscall table),如 9 ,也能夠是 SIGNAME,如 SIGKILL
Syntax: STOPSIGNAL signal

ARG

 在 docker build 過程當中傳參數,使用  --build-arg <varname>=<value> 

FROM nginx:1.14-alpine ARG author="abao <abao@163.com>" LABEL maintainer=${author} ENV NGX_DOC_ROOT="/data/web/html/" ADD index.html ${NGX_DOC_ROOT} ADD entrypoint.sh /bin/ EXPOSE 80/tcp HEALTHCHECK --start-period=3s CMD wget -O - -q http://${IP:-0.0.0.0}:${PORT:-80}/ CMD ["/usr/sbin/nginx","-g","daemon off;"] ENTRYPOINT ["/bin/entrypoint.sh"] # docker build --build-arg author="xiao <xiao@qq.com>" -t myweb:v0.9 ./ # docker image inspect myweb:v0.9 |grep maintainer
                "maintainer": "xiao <xiao@qq.com>"
                "maintainer": "xiao <xiao@qq.com>"

 

 ONBUILD  用於在 Dockerfile 中定義一個觸發器

Dockerfile用於 build 鏡像文件,此鏡像文件能夠做爲 base image 被另外一個 Dockerfile 用作 FROM 指令的參數,並以只構建新的鏡像文件
在後面的這個Dockerfile中的 FROM 指令在 build 過程當中被執行時,將會「觸發」建立其 base image 的 Dockerfile 文件中 ONBUILD 指令定義的觸發器

    base image->build -> image1 -> build(這個過程當中執行ONBUILD)->image2

Syntax: ONBUILD <INSTRUCTION>

  • 儘管任何指令均可以註冊成爲觸發器指令,但ONBUILD不能自我嵌套,且不會觸發FROM和MAINTAINER 指令
  • 使用包含 ONBUILD 指令的 Dockerfile 構建的鏡像應該使用特殊的標籤,例如 ruby:2.0-onbuild
  • 在 ONBUILD 指令中使用 ADD 或 COPY指令應該格外當心,由於新構建過程的上下文在缺乏指定原文件時會失敗
相關文章
相關標籤/搜索