欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

服务计算||docker

程序员文章站 2022-03-24 15:32:48
...

服务计算

使用环境:CentOS 7 3.10.0-957.21.3.el7.x86_64

  • 安装 Docker

    直接使用yum安装

    需要先卸载较旧版本的Docker

    yum remove -y docker \
                      docker-client \
                      docker-client-latest \
                      docker-common \
                      docker-latest \
                      docker-latest-logrotate \
                      docker-logrotate \
                      docker-selinux \
                      docker-engine-selinux \
                      docker-engine
    
    

    安装命令

    yum install -y yum-utils device-mapper-persistent-data lvm2
    yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
    yum install -y docker-ce
    

    启动docker

    systemctl start docker
    

    运行helloworld

    docker run hello-world
    
    

    一开始失败,因为没有安装这个给镜像,然后系统会安装镜像

  • 运行第一个容器

     docker run -d -p 80:80 httpd
    

    结果

    [aaa@qq.com ~]$ sudo docker run -d -p 80:80 httpd
    Unable to find image 'httpd:latest' locally
    latest: Pulling from library/httpd
    000eee12ec04: Pull complete 
    32b8712d1f38: Pull complete 
    f1ca037d6393: Pull complete 
    c4bd3401259f: Pull complete 
    51c60bde4d46: Pull complete 
    Digest: sha256:ac6594daaa934c4c6ba66c562e96f2fb12f871415a9b7117724c52687080d35d
    Status: Downloaded newer image for httpd:latest
    10eaa3f5c32a639f7a07c5b2ec1bf7a0c54ca4216645ee3a515ca0e6ff1e5d2e
    
    

    下面我们可以通过浏览器验证容器是否正常工作。在浏览器中输入 http://http://192.168.122.1/

    服务计算||docker

  • 了解docker命令

    [aaa@qq.com ~]$ sudo docker --help
    [sudo] chen 的密码:
    
    Usage:	docker [OPTIONS] COMMAND
    
    A self-sufficient runtime for containers
    
    Options:
          --config string      Location of client config files (default
                               "/root/.docker")
      -c, --context string     Name of the context to use to connect to the
                               daemon (overrides DOCKER_HOST env var and
                               default context set with "docker context use")
      -D, --debug              Enable debug mode
      -H, --host list          Daemon socket(s) to connect to
      -l, --log-level string   Set the logging level
                               ("debug"|"info"|"warn"|"error"|"fatal")
                               (default "info")
          --tls                Use TLS; implied by --tlsverify
          --tlscacert string   Trust certs signed only by this CA (default
                               "/root/.docker/ca.pem")
          --tlscert string     Path to TLS certificate file (default
                               "/root/.docker/cert.pem")
          --tlskey string      Path to TLS key file (default
                               "/root/.docker/key.pem")
          --tlsverify          Use TLS and verify the remote
      -v, --version            Print version information and quit
    
    Management Commands:
      builder     Manage builds
      config      Manage Docker configs
      container   Manage containers
      context     Manage contexts
      engine      Manage the docker engine
      image       Manage images
      network     Manage networks
      node        Manage Swarm nodes
      plugin      Manage plugins
      secret      Manage Docker secrets
      service     Manage services
      stack       Manage Docker stacks
      swarm       Manage Swarm
      system      Manage Docker
      trust       Manage trust on Docker images
      volume      Manage volumes
    
    Commands:
      attach      Attach local standard input, output, and error streams to a running container
      build       Build an image from a Dockerfile
      commit      Create a new image from a container's changes
      cp          Copy files/folders between a container and the local filesystem
      create      Create a new container
      diff        Inspect changes to files or directories on a container's filesystem
      events      Get real time events from the server
      exec        Run a command in a running container
      export      Export a container's filesystem as a tar archive
      history     Show the history of an image
      images      List images
      import      Import the contents from a tarball to create a filesystem image
      info        Display system-wide information
      inspect     Return low-level information on Docker objects
      kill        Kill one or more running containers
      load        Load an image from a tar archive or STDIN
      login       Log in to a Docker registry
      logout      Log out from a Docker registry
      logs        Fetch the logs of a container
      pause       Pause all processes within one or more containers
      port        List port mappings or a specific mapping for the container
      ps          List containers
      pull        Pull an image or a repository from a registry
      push        Push an image or a repository to a registry
      rename      Rename a container
      restart     Restart one or more containers
      rm          Remove one or more containers
      rmi         Remove one or more images
      run         Run a command in a new container
      save        Save one or more images to a tar archive (streamed to STDOUT by default)
      search      Search the Docker Hub for images
      start       Start one or more stopped containers
      stats       Display a live stream of container(s) resource usage statistics
      stop        Stop one or more running containers
      tag         Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
      top         Display the running processes of a container
      unpause     Unpause all processes within one or more containers
      update      Update configuration of one or more containers
      version     Show the Docker version information
      wait        Block until one or more containers stop, then print their exit codes
    
    Run 'docker COMMAND --help' for more information on a command.
    
    
  • Docker 客户端

    最常用的 Docker 客户端是 docker 命令。通过 docker 我们可以方便地在 Host 上构建和运行容器。

    docker 支持很多操作(子命令),后面会逐步用到。

    服务计算||docker

    除了 docker 命令行工具,用户也可以通过 REST API 与服务器通信。

    Docker 服务器

    Docker daemon 是服务器组件,以 Linux 后台服务的方式运行。

    服务计算||docker

    Docker daemon 运行在 Docker host 上,负责创建、运行、监控容器,构建、存储镜像。

    默认配置下,Docker daemon 只能响应来自本地 Host 的客户端请求。如果要允许远程客户端请求,需要在配置文件中打开 TCP 监听,步骤如下:

    1. 编辑配置文件 /etc/systemd/system/multi-user.target.wants/docker.service,在环境变量 ExecStart 后面添加 -H tcp://0.0.0.0,允许来自任意 IP 的客户端连接。
      服务计算||docker如果使用的是其他操作系统,配置文件的位置可能会不一样。
    2. 重启 Docker daemon。
      服务计算||docker
    3. 服务器 IP 为 192.168.56.102,客户端在命令行里加上 -H 参数,即可与远程服务器通信。
      服务计算||dockerinfo 子命令用于查看 Docker 服务器的信息
  • docker组件如何协作

    [aaa@qq.com ~]$ sudo docker run -d -p 80:80 httpd
    //ocker 客户端执行 docker run 命令。
    Unable to find image 'httpd:latest' locally
    //Docker daemon 发现本地没有 httpd 镜像。
    latest: Pulling from library/httpd
    //daemon 从 Docker Hub 下载镜像。
    000eee12ec04: Pull complete 
    32b8712d1f38: Pull complete 
    f1ca037d6393: Pull complete 
    c4bd3401259f: Pull complete 
    51c60bde4d46: Pull complete 
    Digest: sha256:ac6594daaa934c4c6ba66c562e96f2fb12f871415a9b7117724c52687080d35d
    Status: Downloaded newer image for httpd:latest
    //下载完成,镜像 httpd 被保存到本地。
    10eaa3f5c32a639f7a07c5b2ec1bf7a0c54ca4216645ee3a515ca0e6ff1e5d2e
    //Docker daemon 启动容器。
    
    docker images
    
    [aaa@qq.com ~]$ sudo docker images
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    httpd               latest              2ae34abc2ed0        4 weeks ago         165MB
    hello-world         latest              fce289e99eb9        12 months ago       1.84kB
    [aaa@qq.com ~]$ sudo docker ps
    CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                NAMES
    10eaa3f5c32a        httpd               "httpd-foreground"   10 minutes ago      Up 10 minutes       0.0.0.0:80->80/tcp   hopeful_kare
    [aaa@qq.com ~]$ 
    
    
  • hello-world 最小的镜像

    [aaa@qq.com ~]$ sudo docker pull hello-world
    Using default tag: latest
    latest: Pulling from library/hello-world
    Digest: sha256:4fe721ccc2e8dc7362278a29dc660d833570ec2682f4e4194f4ee23e415e1064
    Status: Image is up to date for hello-world:latest
    docker.io/library/hello-world:latest
    [aaa@qq.com ~]$ sudo dockers images hello-world
    sudo: dockers:找不到命令
    [aaa@qq.com ~]$ sudo docker images hello-world
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    hello-world         latest              fce289e99eb9        12 months ago       1.84kB
    [aaa@qq.com ~]$ 
    
    
    [aaa@qq.comase ~]$ sudo docker run hello-world
    
    Hello from Docker!
    This message shows that your installation appears to be working correctly.
    
    To generate this message, Docker took the following steps:
     1. The Docker client contacted the Docker daemon.
     2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
        (amd64)
     3. The Docker daemon created a new container from that image which runs the
        executable that produces the output you are currently reading.
     4. The Docker daemon streamed that output to the Docker client, which sent it
        to your terminal.
    
    To try something more ambitious, you can run an Ubuntu container with:
     $ docker run -it ubuntu bash
    
    Share images, automate workflows, and more with a free Docker ID:
     https://hub.docker.com/
    
    For more examples and ideas, visit:
     https://docs.docker.com/get-started/
    
    [aaa@qq.com ~]$ 
    
    

    hello-world 的 Dockerfile 内容如下:

    服务计算||docker

    只有短短三条指令。

    1. FROM scratch
      此镜像是从白手起家,从 0 开始构建。
    2. COPY hello /
      将文件“hello”复制到镜像的根目录。
    3. CMD ["/hello"]
      容器启动时,执行 /hello

    镜像 hello-world 中就只有一个可执行文件 “hello”,其功能就是打印出 “Hello from Docker …” 等信息。

    /hello 就是文件系统的全部内容,连最基本的 /bin,/usr, /lib, /dev 都没有。

  • base镜像

    base 镜像有两层含义:

    1. 不依赖其他镜像,从 scratch 构建。
    2. 其他镜像可以之为基础进行扩展。

    下载镜像:

    docker pull centos

    [aaa@qq.com ~]$ sudo docker pull centos
    Using default tag: latest
    latest: Pulling from library/centos
    729ec3a6ada3: Pull complete 
    Digest: sha256:f94c1d992c193b3dc09e297ffd54d8a4f1dc946c37cbeceb26d35ce1647f88d9
    Status: Downloaded newer image for centos:latest
    docker.io/library/centos:latest
    
    

    查看镜像信息:

    [aaa@qq.com ~]$ sudo docker images centos
    [sudo] chen 的密码:
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    centos              latest              0f3e07c0138f        2 months ago        220MB
    
    
  • 构建镜像

    docker commit 命令是创建新镜像最直观的方法,其过程包含三个步骤:

    \1. 运行容器

    \2. 修改容器

    \3. 将容器保存为新的镜像

    1. 第一步, 运行容器

      -it 参数的作用是以交互模式进入容器,并打开终端。412b30588f4a 是容器的内部 ID。

      [aaa@qq.com ~]$ sudo docker run -it ubuntu
      Unable to find image 'ubuntu:latest' locally
      latest: Pulling from library/ubuntu
      2746a4a261c9: Pull complete 
      4c1d20cdee96: Pull complete 
      0d3160e1d0de: Pull complete 
      c8e37668deea: Pull complete 
      Digest: sha256:250cc6f3f3ffc5cdaa9d8f4946ac79821aafb4d3afc93928f0de9336eba21aa4
      Status: Downloaded newer image for ubuntu:latest
      aaa@qq.com:/# 
      
      
    2. 安装 vi

      aaa@qq.com:/# vim
      bash: vim: command not found
      

      确认 vi 没有安装。

      更新apt-get

      apt-get update
      apt-get install vim -y
      
    3. 保存为新镜像
      在新窗口中查看当前运行的容器

      docker ps

      [aaa@qq.com ~]$ sudo docker ps
      [sudo] chen 的密码:
      CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                NAMES
      1d759aafe85c        ubuntu              "/bin/bash"          9 minutes ago       Up 9 minutes                             trusting_mcclintock
      10eaa3f5c32a        httpd               "httpd-foreground"   41 minutes ago      Up 41 minutes       0.0.0.0:80->80/tcp   hopeful_kare
      
      
      

      docker commit执行 docker commit 命令将容器保存为镜像。

      新镜像命名为 ubuntu-with-vi

      [aaa@qq.com ~]$ sudo docker commit trusting_mcclintock ubuntu-with-vi
      sha256:7353f9cbdc6df58e0b1d35a02311da47e51b436916f79f43c56187fd0862615d
      [aaa@qq.com ~]$ sudo docker images
      REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
      ubuntu-with-vi      latest              7353f9cbdc6d        13 seconds ago      152MB
      ubuntu              latest              549b9b86cb8d        8 days ago          64.2MB
      httpd               latest              2ae34abc2ed0        4 weeks ago         165MB
      centos              latest              0f3e07c0138f        2 months ago        220MB
      hello-world         latest              fce289e99eb9        12 months ago       1.84kB
      [aaa@qq.com ~]$ 
      

      查看新镜像的属性。从 size 上看到镜像因为安装了软件而变大了。

      从新镜像启动容器,验证 vi 已经可以使用。

  • Dockerfile 构建镜像

    创建dockerfile文件

    touch Dockerfile
    

    Dockerfile文件内容

    服务计算||docker

    运行

    docker build -t ubuntu-with-vi-dockerfile .  
    

    安装成功

    服务计算||docker

    查看熟悉

    docker images

    [aaa@qq.com ~]# sudo docker images
    REPOSITORY                  TAG                 IMAGE ID            CREATED             SIZE
    ubuntu-with-vi-dockerfile   latest              79e6c9a350a0        36 seconds ago      286MB
    ubuntu-with-vi              latest              7353f9cbdc6d        27 minutes ago      152MB
    ubuntu                      latest              549b9b86cb8d        8 days ago          64.2MB
    httpd                       latest              2ae34abc2ed0        4 weeks ago         165MB
    centos                      latest              0f3e07c0138f        2 months ago        220MB
    hello-world                 latest              fce289e99eb9        12 months ago       1.84kB
    [aaa@qq.com ~]# 
    
    
  • 镜像的缓存特性

    在前面的 Dockerfile 中添加一点新内容,往镜像中复制一个文件:

  • 调试dockerfile

  • RUN vs CMD vs ENTRYPOINT

  • 使用公共 Registry

    1. 首先得在 Docker Hub 上注册一个账号。

    2. 在 Docker Host 上登录。

      [aaa@qq.com ~]# docker login -u chentf5 
      Password: 
      WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
      Configure a credential helper to remove this warning. See
      https://docs.docker.com/engine/reference/commandline/login/#credentials-store
      
      Login Succeeded
      
      
    3. 修改镜像的 repository 使之与 Docker Hub 账号匹配。
      Docker Hub 为了区分不同用户的同名镜像,镜像的 registry 中要包含用户名,完整格式为:[username]/xxx:tag
      我们通过 docker tag 命令重命名镜像。

      [aaa@qq.com ~]# docker tag httpd chentf5/httpd:v1
      [aaa@qq.com ~]# docker images chentf5/httpd
      REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
      chentf5/httpd       v1                  2ae34abc2ed0        4 weeks ago         165MB
      [aaa@qq.com ~]# docker push chentf5/httpd:v1
      
      

      注:Docker 官方自己维护的镜像没有用户名,比如 httpd。

    4. 通过 docker push 将镜像上传到 Docker Hub。

      Docker 会上传镜像的每一层。因为 cloudman6/httpd:v1 这个镜像实际上跟官方的 httpd 镜像一模一样,Docker Hub 上已经有了全部的镜像层,所以真正上传的数据很少。同样的,如果我们的镜像是基于 base 镜像的,也只有新增加的镜像层会被上传。如果想上传同一 repository 中所有镜像,省略 tag 部分就可以了,例如:
      docker push cloudman6/httpd

      [aaa@qq.com ~]# docker push chentf5/httpd:v1
      The push refers to repository [docker.io/chentf5/httpd]
      3596c568ff88: Mounted from library/httpd 
      0b56a8e92b13: Mounted from library/httpd 
      133d2fad03ff: Pushed 
      83c5929f7818: Mounted from library/httpd 
      831c5620387f: Pushed 
      v1: digest: sha256:f8a3e48d46d2b90386636264c2024173d30be1443aac88faf3da455fce15ce72 size: 1367
      
      
    5. 登录 https://hub.docker.com,在Public Repository 中就可以看到上传的镜像。

      服务计算||docker

      如果要删除上传的镜像,只能在 Docker Hub 界面上操作。

    6. 这个镜像可被其他 Docker host 下载使用了。

  • 搭建本地 Registry

    启动 registry 容器。

    [aaa@qq.com ~]# docker run -d -p 5000:5000 -v /myregistry:/var/lib/registry registry:2
    Unable to find image 'registry:2' locally
    2: Pulling from library/registry
    c87736221ed0: Pull complete 
    1cc8e0bb44df: Pull complete 
    54d33bcb37f5: Pull complete 
    e8afc091c171: Pull complete 
    b4541f6d3db6: Pull complete 
    Digest: sha256:8004747f1e8cd820a148fb7499d71a76d45ff66bac6a29129bfdbfdc0154d146
    Status: Downloaded newer image for registry:2
    79b55188a9f044c43623a7b8eb8d2f9668e57be90cee63191d7fa392816a92ae
    
    

    通过 docker tag 重命名镜像,使之与 registry 匹配。

    [aaa@qq.com ~]# docker tag chentf5/httpd:v1 127.0.0.1:5000/chentf5/httpd:v1
    [aaa@qq.com ~]# sudo docker push 127.0.0.1:5000/chentf5/httpd:v1
    The push refers to repository [127.0.0.1:5000/chentf5/httpd]
    
    

    通过 docker pull 上传镜像。

    The push refers to repository [127.0.0.1:5000/chentf5/httpd]
    3596c568ff88: Pushed 
    0b56a8e92b13: Pushed 
    133d2fad03ff: Pushed 
    83c5929f7818: Pushed 
    831c5620387f: Pushed 
    v1: digest: sha256:f8a3e48d46d2b90386636264c2024173d30be1443aac88faf3da455fce15ce72 size: 1367
    
    

    删除下载镜像

    [aaa@qq.com ~]# sudo docker rmi 127.0.0.1:5000/chentf5/httpd:v1
    Untagged: 127.0.0.1:5000/chentf5/httpd:v1
    Untagged: 127.0.0.1:5000/chentf5/aaa@qq.com:f8a3e48d46d2b90386636264c2024173d30be1443aac88faf3da455fce15ce72
    [aaa@qq.com ~]# sudo docker pull 127.0.0.1:5000/chentf5/httpd:v1
    v1: Pulling from chentf5/httpd
    Digest: sha256:f8a3e48d46d2b90386636264c2024173d30be1443aac88faf3da455fce15ce72
    Status: Downloaded newer image for 127.0.0.1:5000/chentf5/httpd:v1
    127.0.0.1:5000/chentf5/httpd:v1
    [aaa@qq.com ~]# 
    
    

    服务计算||docker

  • 如何自定义容器网络?

    我们可通过 bridge 驱动创建类似前面默认的 bridge 网络,例如:

    我们可通过 bridge 驱动创建类似前面默认的 bridge 网络,例如:

    [aaa@qq.com ~]# docker network create --driver bridge my_net
    adabae7d3d5132c0f2997c20270b2263ec28940ae40b7e793473a453cb4eeded
    
    

    查看一下当前 host 的网络结构变化:

    [aaa@qq.com ~]# brctl show
    bridge name	bridge id		STP enabled	interfaces
    br-85876ba1d740		8000.02420b24ffcf	no		
    br-adabae7d3d51		8000.0242c6bcde10	no		
    docker0		8000.0242e4bbc687	no		veth8c07daf
    							vethfbf021f
    virbr0		8000.525400fe55f8	yes		virbr0-nic
    [aaa@qq.com ~]# docker network inspect my_net
    
    

    新增了一个网桥 br-eaed97dc9a77,这里 eaed97dc9a77 正好新建 bridge 网络 my_net 的短 id。执行 docker network inspect 查看一下 my_net 的配置信息:

    [aaa@qq.com ~]# docker network inspect my_net
    [
        {
            "Name": "my_net",
            "Id": "adabae7d3d5132c0f2997c20270b2263ec28940ae40b7e793473a453cb4eeded",
            "Created": "2019-12-27T18:46:31.254486254+08:00",
            "Scope": "local",
            "Driver": "bridge",
            "EnableIPv6": false,
            "IPAM": {
                "Driver": "default",
                "Options": {},
                "Config": [
                    {
                        "Subnet": "172.19.0.0/16",
                        "Gateway": "172.19.0.1"
                    }
                ]
            },
            "Internal": false,
            "Attachable": false,
            "Ingress": false,
            "ConfigFrom": {
                "Network": ""
            },
            "ConfigOnly": false,
            "Containers": {},
            "Options": {},
            "Labels": {}
        }
    ]
    
    

    这里 172.18.0.0/16 是 Docker 自动分配的 IP 网段。

    我们可以自己指定 IP 网段吗?
    答案是:可以。

    只需在创建网段时指定 --subnet--gateway 参数:

    [aaa@qq.com ~]# docker network create --driver bridge --subnet 172.22.16.0/24 --gateway 172.22.16.1 my_net2
    86adb34f823b0541d9e0821a605c0505bb609d789cfff3b42e434cd2262c23e4
    [aaa@qq.com ~]# docker nerwork inspect my_net2
    docker: 'nerwork' is not a docker command.
    See 'docker --help'
    [aaa@qq.com ~]# docker network inspect my_net2
    [
        {
            "Name": "my_net2",
            "Id": "86adb34f823b0541d9e0821a605c0505bb609d789cfff3b42e434cd2262c23e4",
            "Created": "2019-12-27T18:48:50.212167647+08:00",
            "Scope": "local",
            "Driver": "bridge",
            "EnableIPv6": false,
            "IPAM": {
                "Driver": "default",
                "Options": {},
                "Config": [
                    {
                        "Subnet": "172.22.16.0/24",
                        "Gateway": "172.22.16.1"
                    }
                ]
            },
            "Internal": false,
            "Attachable": false,
            "Ingress": false,
            "ConfigFrom": {
                "Network": ""
            },
            "ConfigOnly": false,
            "Containers": {},
            "Options": {},
            "Labels": {}
        }
    ]
    
    

    这里我们创建了新的 bridge 网络 my_net2,网段为 172.22.16.0/24,网关为 172.22.16.1。与前面一样,网关在 my_net2 对应的网桥 br-5d863e9f78b6 上:

    [aaa@qq.com ~]# ifconfig  br-86adb34f823b
    br-86adb34f823b: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
            inet 172.22.16.1  netmask 255.255.255.0  broadcast 172.22.16.255
            ether 02:42:07:d5:77:45  txqueuelen 0  (Ethernet)
            RX packets 0  bytes 0 (0.0 B)
            RX errors 0  dropped 0  overruns 0  frame 0
            TX packets 0  bytes 0 (0.0 B)
            TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
    
    
    

    容器要使用新的网络,需要在启动时通过 --network 指定:

    [aaa@qq.com ~]# docker run -it --network=my_net2 busybox
    Unable to find image 'busybox:latest' locally
    docker: Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: TLS handshake timeout.
    See 'docker run --help'.
    [aaa@qq.com ~]# docker run -it --network=my_net2 busybox
    Unable to find image 'busybox:latest' locally
    docker: Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: TLS handshake timeout.
    See 'docker run --help'.
    

    这里一直失败。。。

    容器分配到的 IP 为 172.22.16.2。

    到目前为止,容器的 IP 都是 docker 自动从 subnet 中分配,我们能否指定一个静态 IP 呢?

    答案是:可以,通过--ip指定。

    服务计算||docker

    注:只有使用 --subnet 创建的网络才能指定静态 IP

    my_net 创建时没有指定 --subnet,如果指定静态 IP 报错如下:

    服务计算||docker

相关标签: 技术