zoukankan      html  css  js  c++  java
  • 二进制部署k8s1.12

    二进制部署k8s

    1.介绍

    • k8s是谷歌开源容器集群管理系统,它可以应用于程序部署扩展和管理,它提供了容器编排,资源调度,弹性伸缩,部署管理服务发现等一系列功能。

    2.k8s的集群架构与组件

    主节点Master运行着:API Server, scheduler, controller-manager三个节点
    工作节点Node运行着:kubelet, kube-proxy
    
    • Master组件
    kube-apiserver
    	集群统一入口,各组件协调者,以restfulAPI提供接口服务,所有对象资源的增删改查和监听操作都交给APIServer处理后提交给ETCD存储。
    
    kube-controller-manager
    	处理集群中常规后台任务,一个资源对应一个控制器,而ControllerManager就是负责管理这些控制器的
    kube-scheduler
    	根据调度算法为新的创建Pod选择一个Node节点,可以任意部署,可以部署在一个节点上,也可以部署在不同节点上
    etcd
    	分布式键值存储系统,用于保存集群状态数据,比如pod service等对象信息
    
    • Node组件
    kubelet
    	它是Master在Node节点上Agent,管理本机运行容器生命周期,比如:创建容器,Pod挂载卷,下载secret,获取若能国企和节点状态工作,kubelet将每个Pod转换成一组容器
    kube-proxy
    	在Node节点上实现Pod网络代理,维护网络规则和四层负载均衡
    docker  rocker
    	容器引擎,运行容器
    

    3.K8s核心概念

    • Pod部署应用的最小单元

      它是最小部署单元,它一组容器的集合,而且一个Pod中的可能有多个容器,容器之间网络是共享(当一个容器监听8080端口,其他的容器也能看见,并且通过k8s数据卷也能实现容器之间的数据的共享),而它们文件系统是隔离的。Pod是短暂的,当你触发服务更新或发布,那么之前pod就会销毁。

    • Controllers控制器

      1.ReplicaSet: 确保预期的Pod副本数量

      2.Deployment:用于无状态应用部署。

      3.StatefulSet:用于有状态应用部署

      4.DaemonSet:确保所有Node运行同一个Pod

      5.Job:一次性任务。

      6.Cronjob: 定时任务。

    • Service服务转发

      1.防止Pod失去联系,定义一组Pod的访问策略

    • Label 标签

      标签,附加在某个资源上,用于关联对象,查询和筛选。

    • Namespace

      命名空间,将对象逻辑上隔离

    什么是无状态应用和有状态的应用??
    无状态:deployment
      - 认为所有pod都是一样的,不具备与其他实例有不同的关系。
      - 没有顺序的要求。
      - 不用考虑再哪个Node运行。
      - 随意扩容缩容。
    
    有状态:SatefulSet
      - 集群节点之间的关系。
      - 数据不完全一致。
      - 实例之间不对等的关系。
      - 依靠外部存储的应用。
      - 通过dns维持身份
      像MySQl主从,Zookepeer
    

    4.K8s部署前言:

    • 官方提供的三种部署方式:

      • minikube

        Minikube是一个工具,可以在本地快速运行一个单点Kubernetes,仅用于尝试kubernetes或日常开发的用户。

        网址

      • kubeadm

        Kubeadm也是一个工具,提供kubeadm init 和kubeadm join 用于快速部署Kubernetes集群。

        网址

      • 二进制

        手动部署每个组件

        网址

    • 软件版本:

      软件名称 版本号
      Linux操作系统 CentOs7.5_x64 +
      Kubemetes 1.13
      Etcd 3.+
      Docker 18.+
      Flannel 0.10
    • 角色对应关系:

    角色 部署IP 组件
    k8s-master01 192.168.48.128 kube-apiserver,kube-controller-manager,kube-scheduler,etcd
    k8s-node01 192.168.48.130 kubelet,kube-proxy,docker,flannel,etcd
    k8s-node02 192.168.48.131 kubelet, kube-proxy,docker,flannel,etcd
    • 部署架构图:

    5.前续工作:

    • 关闭各所有机器SELINUX和防火墙

      vi /etc/selinux/config 
      SELINUX=disabled
      setenforce 0
      systemctl stop firewalld
      
      # 验证:iptables -vnL
      
    • 修改主机名

      # 192.168.48.128 改成 k8s-master01
      vi /etc/hostname
      hostname k8s-master01
      exit退出终端再进入。
      
      以相同方式更改node01和node02的名字:
      # 192.168.48.130 改成 k8s-node01
      # 192.168.48.131 改成 k8s-node02
      

    6.HTTPS证书构建

    • 因为apiServer 与 kubelet之间通信是https, etcd(数据库)与 Flannel之间通信也是https

    • 自签SSL证书:我们需要构建2套证书,ca证书,和api-server证书。【ca.pem,server.pem代表证书, server-key代表私钥】

      apiserver 加 ca-key.pem
      
    • 为etcd生成证书

      # 时间同步:(所有机器都同步)
      	下载ntpdate: yum install ntpdate
      	更新最新时间: ntpdate time.windows.com
      # 首先需要下载cfssl工具
      	执行脚本:[/root/k8s/etcd-cert:] bash cfssl.sh
      # 生成ca:# 执行下面命令会生成ca-config.json,ca-csr.json两个文件。
      cat > ca-config.json <<EOF
      {
        "signing": {
          "default": {
            "expiry": "87600h"
          },
          "profiles": {
            "www": {
               "expiry": "87600h",
               "usages": [
                  "signing",
                  "key encipherment",
                  "server auth",
                  "client auth"
              ]
            }
          }
        }
      }
      EOF
      
      cat > ca-csr.json <<EOF
      {
          "CN": "etcd CA",
          "key": {
              "algo": "rsa",
              "size": 2048
          },
          "names": [
              {
                  "C": "CN",
                  "L": "Beijing",
                  "ST": "Beijing"
              }
          ]
      }
      EOF
      # 生成根证书:initca 初始化ca-csr.json 配置文件
      cfssl gencert -initca ca-csr.json | cfssljson -bare ca -
      
      # 生成etcd域名证书:配置部署etcd节点所有的IP
      cat > server-csr.json <<EOF
      {
          "CN": "etcd",
          "hosts": [
          "192.168.48.128",
          "192.168.48.130",
          "192.168.48.131"
          ],
          "key": {
              "algo": "rsa",
              "size": 2048
          },
          "names": [
              {
                  "C": "CN",
                  "L": "BeiJing",
                  "ST": "BeiJing"
              }
          ]
      }
      EOF
      # 请求ca颁发证书:
      cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=www server-csr.json | cfssljson -bare server
      # 此时会生成:server.pem 和 server-key.pem 而etcd使用这2个证书即可
      

    7.ETCD数据库集群部署

    • 准备工作
    [root@k8s-master01 ~]# mkdir soft
    [root@k8s-master01 ~]# cd soft/
    # 解压之前下载好的etcd包:etcd-v3.3.10-linux-amd64.tar.gz
    tar -zxvf etcd-v3.3.10-linux-amd64.tar.gz
    [root@k8s-master01 soft]# cd etcd-v3.3.10-linux-amd64
    [root@k8s-master01 etcd-v3.3.10-linux-amd64]# ls
    Documentation  etcd  etcdctl  README-etcdctl.md  README.md  READMEv2-etcdctl.md
    # etcd 启动etcd服务
    # etcdctl 管理etcd客户端命令
    # 创建etcd管理目录:
    mkdir /opt/etcd/{cfg,bin,ssl} -p
    # 将 etcd,etcdctl 移动到 /opt/etcd/bin
    mv etcd etcdctl /opt/etcd/bin/
    
    • etcd部署脚本(一般生产环境使用5台,但最小使用3台):
    写etcd.sh脚本:
    #!/bin/bash
    # example: ./etcd.sh etcd01 192.168.1.10 etcd02=https://192.168.1.11:2380,etcd03=https://192.168.1.12:2380
    
    ETCD_NAME=$1
    ETCD_IP=$2
    ETCD_CLUSTER=$3
    # 工作目录定义
    WORK_DIR=/opt/etcd
    # 生成etcd配置文件
    # Member中 2380端口设置集群之间通信,2379是数据传输端口
    # Clustering 集群配置信息
    
    
    cat <<EOF >$WORK_DIR/cfg/etcd
    #[Member]
    ETCD_NAME="${ETCD_NAME}"
    ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
    ETCD_LISTEN_PEER_URLS="https://${ETCD_IP}:2380"
    ETCD_LISTEN_CLIENT_URLS="https://${ETCD_IP}:2379"
    
    #[Clustering]
    ETCD_INITIAL_ADVERTISE_PEER_URLS="https://${ETCD_IP}:2380"
    ETCD_ADVERTISE_CLIENT_URLS="https://${ETCD_IP}:2379"
    ETCD_INITIAL_CLUSTER="etcd01=https://${ETCD_IP}:2380,${ETCD_CLUSTER}"
    ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
    ETCD_INITIAL_CLUSTER_STATE="new"
    EOF
    # 配置证书选项
    cat <<EOF >/usr/lib/systemd/system/etcd.service
    [Unit]
    Description=Etcd Server
    After=network.target
    After=network-online.target
    Wants=network-online.target
    
    [Service]
    Type=notify
    EnvironmentFile=${WORK_DIR}/cfg/etcd
    ExecStart=${WORK_DIR}/bin/etcd 
    --name=${ETCD_NAME} 
    --data-dir=${ETCD_DATA_DIR} 
    --listen-peer-urls=${ETCD_LISTEN_PEER_URLS} 
    --listen-client-urls=${ETCD_LISTEN_CLIENT_URLS},http://127.0.0.1:2379 
    --advertise-client-urls=${ETCD_ADVERTISE_CLIENT_URLS} 
    --initial-advertise-peer-urls=${ETCD_INITIAL_ADVERTISE_PEER_URLS} 
    --initial-cluster=${ETCD_INITIAL_CLUSTER} 
    --initial-cluster-token=${ETCD_INITIAL_CLUSTER_TOKEN} 
    --initial-cluster-state=new 
    --cert-file=${WORK_DIR}/ssl/server.pem 
    --key-file=${WORK_DIR}/ssl/server-key.pem 
    --peer-cert-file=${WORK_DIR}/ssl/server.pem 
    --peer-key-file=${WORK_DIR}/ssl/server-key.pem 
    --trusted-ca-file=${WORK_DIR}/ssl/ca.pem 
    --peer-trusted-ca-file=${WORK_DIR}/ssl/ca.pem
    Restart=on-failure
    LimitNOFILE=65536
    
    [Install]
    WantedBy=multi-user.target
    EOF
    # 启动命令
    systemctl daemon-reload
    systemctl enable etcd
    systemctl restart etcd
    
    # 赋予执行权限
    chmod +x etcd.sh
    
    • 使用方式
    ./etcd.sh 当前节点名 当前节点ip 节点2的名=https://节点2的ip:2380,节点3的名=https://节点2的ip:2380
    
    #示例:
    ./etcd.sh etcd01 192.168.48.128 etcd02=https://192.168.48.130:2380,etcd03=https://192.168.48.131:2380
    # 执行脚本:
    ./etcd.sh etcd01 192.168.48.128 etcd02=https://192.168.48.130:2380,etcd03=https://192.168.48.131:2380
    
    • 查看,检查配置文件:
    #############################1#############################
    cat /opt/etcd/cfg/etcd
    #[Member]
    ETCD_NAME="etcd01"# 当前节点名字
    ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
    ETCD_LISTEN_PEER_URLS="https://192.168.48.128:2380"
    ETCD_LISTEN_CLIENT_URLS="https://192.168.48.128:2379"
    
    #[Clustering]
    ETCD_INITIAL_ADVERTISE_PEER_URLS="https://192.168.48.128:2380"# 2380端口设置集群之间通信
    ETCD_ADVERTISE_CLIENT_URLS="https://192.168.48.128:2379"# 2379是数据传输端口
    ETCD_INITIAL_CLUSTER="etcd01=https://192.168.48.128:2380,etcd02=https://192.168.48.130:2380,etcd03=https://192.168.48.131:2380"# 集群中节点
    ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"# token认证
    ETCD_INITIAL_CLUSTER_STATE="new"# 代表新的集群
    ###############################2############################
    cat /usr/lib/systemd/system/etcd.service 
    [Unit]
    Description=Etcd Server
    After=network.target
    After=network-online.target
    Wants=network-online.target
    
    [Service]
    Type=notify
    EnvironmentFile=/opt/etcd/cfg/etcd
    ExecStart=/opt/etcd/bin/etcd --name=${ETCD_NAME} --data-dir=${ETCD_DATA_DIR} --listen-peer-urls=${ETCD_LISTEN_PEER_URLS} --listen-client-urls=${ETCD_LISTEN_CLIENT_URLS},http://127.0.0.1:2379 --advertise-client-urls=${ETCD_ADVERTISE_CLIENT_URLS} --initial-advertise-peer-urls=${ETCD_INITIAL_ADVERTISE_PEER_URLS} --initial-cluster=${ETCD_INITIAL_CLUSTER} --initial-cluster-token=${ETCD_INITIAL_CLUSTER_TOKEN} --initial-cluster-state=new --cert-file=/opt/etcd/ssl/server.pem --key-file=/opt/etcd/ssl/server-key.pem --peer-cert-file=/opt/etcd/ssl/server.pem --peer-key-file=/opt/etcd/ssl/server-key.pem --trusted-ca-file=/opt/etcd/ssl/ca.pem --peer-trusted-ca-file=/opt/etcd/ssl/ca.pem
    Restart=on-failure
    LimitNOFILE=65536
    
    [Install]
    WantedBy=multi-user.target
    
    • 将证书拷贝到/opt/etcd/ssl
    cp ~/k8s/etcd-cert/{ca,server-key,server}.pem /opt/etcd/ssl
    
    ls /opt/etcd/ssl 查看证书
    ca.pem  server-key.pem  server.pem
    
    • 启动etcd
    systemctl start etcd
    
    • 查看启动日志:
    tail /var/log/messages -f
    
    • 将etcd配置拷贝其他2台机器上
    scp -r /opt/etcd/ root@192.168.48.130:/opt/
    scp -r /opt/etcd/ root@192.168.48.131:/opt/
    
    • 将etcd.service拷贝到其他2台机器
    scp /usr/lib/systemd/system/etcd.service root@192.168.48.130:/usr/lib/systemd/system
    scp /usr/lib/systemd/system/etcd.service root@192.168.48.131:/usr/lib/systemd/system
    
    • 修改192.168.48.130, 192.168.48.131配置文件
    vim /opt/etcd/cfg/etcd
    
    修改ETCD_NAME,ETCD_LISTEN_PEER_URLS, ETCD_LISTEN_CLIENT_URLS,ETCD_INITIAL_ADVERTISE_PEER_URLS,ETCD_ADVERTISE_CLIENT_URLS
    
    • daemon-reload 和 启动
    systemctl daemon-reload
    systemctl start etcd
    
    • 验证etcd集群状态
    /opt/etcd/bin/etcdctl --ca-file=/opt/etcd/ssl/ca.pem --cert-file=/opt/etcd/ssl/server.pem --key-file=/opt/etcd/ssl/server-key.pem --endpoints="https://192.168.48.128:2379,https://192.168.48.130:2379,https://192.168.48.131:2379"  cluster-health
    
    • 下面结果显示代表集群健康:
    member 3f3868c5ec7337c4 is healthy: got healthy result from https://192.168.48.128:2379
    member c3e65c73ad3cc87a is healthy: got healthy result from https://192.168.48.131:2379
    member c4f45bf163b686e6 is healthy: got healthy result from https://192.168.48.130:2379
    cluster is healthy
    

    8.node安装docker

    • yum安装必要的组件

      yum install -y yum-utils device-mapper-persistent-data lvm2
      
    • 添加软件源信息

      yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
      
    • 安装docker

      yum -y install docker-ce
      
    • 启动docker

      systemctl start docker
      
    • daocloud配置docker加速器

      curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://f1361db2.m.daocloud.io
      
    • 重启docker

      systemctl restart docker
      

    9.Kubernetes网络模型(CNI)

    • CNI容器网络接口。Kubernetes网络模型设计基本要求:

      1. 一个Pod一个ip
      2. 每个Pod独立IP,Pod内所有容器共享网络(同一个IP)
      3. 所有容器都可以与所有其他容器通信
      4.所有节点都可以与所有容器通信
      

      而实现上面要求实现方案为:隧道方案或路由方案,相比来说路由方案网络开销很小,常用实现组件:flannel和calico.flannel适用于小规模的,它局限于统一局域网之内,它对网络适应能力强。Calico基于BGP协议进行通信,它有很多网络通信是不支持的。大规模的适合calico,小规模适合flannel。

    10.部署Kubernetes网络-Flannel

    master节点

    • 为k8s分配子网,在Master主机上设置:

      /opt/etcd/bin/etcdctl --ca-file=/opt/etcd/ssl/ca.pem --cert-file=/opt/etcd/ssl/server.pem --key-file=/opt/etcd/ssl/server-key.pem --endpoints="https://192.168.48.128:2379,https://192.168.48.130:2379,https://192.168.48.131:2379"  set /coreos.com/network/config '{"Network":"172.17.0.0/16", "Backend": {"Type": "vxlan"}}'
      

    node节点

    • node节点创建工作目录

      mkdir /opt/kubernetes/{bin,cfg,ssl} -p
      
    • 下载flannel二进制包

      flannel-v0.10.0-linux-amd64.tar.gz
      
    • 解压flannel包

      tar zxvf flannel-v0.10.0-linux-amd64.tar.gz
      
    • 将解压生成的flanneld 和 mk-docker-opts.sh移动到/opt/kubernetes/bin/

      mv flanneld mk-docker-opts.sh /opt/kubernetes/bin/
      
    • shell安装 flannel

      # 生成flannel配置文件,指定使用etcd地址
      cat <<EOF >/opt/kubernetes/cfg/flanneld
      
      FLANNEL_OPTIONS="--etcd-endpoints=${ETCD_ENDPOINTS} 
      -etcd-cafile=/opt/etcd/ssl/ca.pem 
      -etcd-certfile=/opt/etcd/ssl/server.pem 
      -etcd-keyfile=/opt/etcd/ssl/server-key.pem"
      
      EOF
      
      # 生成flannel.service配置文件
      cat <<EOF >/usr/lib/systemd/system/flanneld.service
      [Unit]
      Description=Flanneld overlay address etcd agent
      After=network-online.target network.target
      Before=docker.service
      
      [Service]
      Type=notify
      EnvironmentFile=/opt/kubernetes/cfg/flanneld
      ExecStart=/opt/kubernetes/bin/flanneld --ip-masq $FLANNEL_OPTIONS
      ExecStartPost=/opt/kubernetes/bin/mk-docker-opts.sh -k DOCKER_NETWORK_OPTIONS -d /run/flannel/subnet.env
      Restart=on-failure
      
      [Install]
      WantedBy=multi-user.target
      
      EOF
      
      # 修改docker配置文件
      
      cat <<EOF >/usr/lib/systemd/system/docker.service
      
      [Unit]
      Description=Docker Application Container Engine
      Documentation=https://docs.docker.com
      After=network-online.target firewalld.service
      Wants=network-online.target
      
      [Service]
      Type=notify
      EnvironmentFile=/run/flannel/subnet.env
      ExecStart=/usr/bin/dockerd $DOCKER_NETWORK_OPTIONS
      ExecReload=/bin/kill -s HUP $MAINPID
      LimitNOFILE=infinity
      LimitNPROC=infinity
      LimitCORE=infinity
      TimeoutStartSec=0
      Delegate=yes
      KillMode=process
      Restart=on-failure
      StartLimitBurst=3
      StartLimitInterval=60s
      
      [Install]
      WantedBy=multi-user.target
      
      EOF
      
      systemctl daemon-reload
      systemctl enable flanneld
      systemctl restart flanneld
      systemctl restart docker
      
      
      
      
    • 执行脚本,后面参数指定etcd的地址

      ./flannel.sh https://192.168.48.128:2379,https://192.168.48.130:2379,https://192.168.48.131:2379
      
    • 查看flanneld配置是否生效

      cat /opt/kubernetes/cfg/flanneld
      
    • 查看flannel.service配置

      cat /usr/lib/systemd/system/flanneld.service 
      
    • 启动flannel

      systemctl start flanneld
      
    • 查看启动状态

      ps -ef | grep flannel
      
    • 查看启动失败日志

      tail /var/log/messages -f
      
    • 重启docker,看是否引用flannel分配的子网

      [root@k8s-node02 ~]# systemctl restart docker
      [root@k8s-node02 ~]# ps -ef | grep docker
      root       2613      1  1 23:25 ?        00:00:00 /usr/bin/dockerd --bip=172.17.44.1/24 --ip-masq=false --mtu=1450
      root       2753   1351  0 23:26 pts/0    00:00:00 grep --color=auto docker
      
      # --bip看到引用的子网了,也可以用ifconfig查看docker0的ip
      

    拷贝其他node节点拷贝

    scp -r /opt/kubernetes/ root@192.168.48.131:/opt/
    scp /usr/lib/systemd/system/{flanneld,docker}.service root@192.168.48.131:/usr/lib/systemd/system
    # 启动flannel
    systemctl start flannel
    # 重启docker
    systemctl restart docker
    

    验证:

    在node1运行容器并进入:docker run -it busybox
    ifconfig查看容器ip
    在node2 去 ping 该ip看是否通
    
    在node1与node2的容器内互相ping
    
    • etcd查看运行node节点
    [root@k8s-master01 k8s]# /opt/etcd/bin/etcdctl --ca-file=/opt/etcd/ssl/ca.pem --cert-file=/opt/etcd/ssl/server.pem --key-file=/opt/etcd/ssl/server-key.pem --endpoints="https://192.168.48.128:2379,https://192.168.48.130:2379,https://192.168.48.131:2379"  get /coreos.com/network/subnets
    /coreos.com/network/subnets: is a directory
    [root@k8s-master01 k8s]# /opt/etcd/bin/etcdctl --ca-file=/opt/etcd/ssl/ca.pem --cert-file=/opt/etcd/ssl/server.pem --key-file=/opt/etcd/ssl/server-key.pem --endpoints="https://192.168.48.128:2379,https://192.168.48.130:2379,https://192.168.48.131:2379"  ls /coreos.com/network/subnets/
    /coreos.com/network/subnets/172.17.68.0-24
    /coreos.com/network/subnets/172.17.44.0-24
    [root@k8s-master01 k8s]# /opt/etcd/bin/etcdctl --ca-file=/opt/etcd/ssl/ca.pem --cert-file=/opt/etcd/ssl/server.pem --key-file=/opt/etcd/ssl/server-key.pem --endpoints="https://192.168.48.128:2379,https://192.168.48.130:2379,https://192.168.48.131:2379"  get /coreos.com/network/subnets/172.17.68.0-24
    {"PublicIP":"192.168.48.130","BackendType":"vxlan","BackendData":{"VtepMAC":"5a:5f:22:12:e8:62"}}
    

    11.k8s Master组件部署

    11.1api-server

    • 官网下载kubernetes包,上传master服务端
    kubernetes-server-linux-amd64.tar.gz  1.13版本
    
    • 解压kubernetes-server包
    tar zxvf kubernetes-server-linux-amd64.tar.gz
    
    • master创建kubernetes管理目录
    mkdir -p /opt/kubernetes/{bin,cfg,ssl}
    
    • cd到解压后kubernetes/server/bin中
    cd kubernetes/server/bin
    
    • 将kube-apiserver kube-controller-manager kube-scheduler 拷贝到 /opt/kubernetes/bin下
    cp kube-apiserver kube-controller-manager kube-scheduler /opt/kubernetes/bin/
    
    • 编写apiserver.sh脚本
    #!/bin/bash
    
    MASTER_ADDRESS=$1# 指定当前apiserver地址
    ETCD_SERVERS=$2# 指定etcd地址
    # 生成apiserver配置文件
    cat <<EOF >/opt/kubernetes/cfg/kube-apiserver
    
    KUBE_APISERVER_OPTS="--logtostderr=false \
    --log-dir=/opt/kubernetes/logs \# 日志目录
    --v=4 \#日志级别
    --etcd-servers=${ETCD_SERVERS} \# etcd-server地址
    --bind-address=${MASTER_ADDRESS} \#绑定ip地址
    --secure-port=6443 \#apiserver 地址
    --advertise-address=${MASTER_ADDRESS} \# 集群通告地址
    --allow-privileged=true \# 授权是否允许(容器层面的)
    --service-cluster-ip-range=10.0.0.0/24 \# 指定负载均衡ip
    --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota,NodeRestriction \# 启用准入插件
    --authorization-mode=RBAC,Node \#认证模式
    --kubelet-https=true \# 启用https
    --enable-bootstrap-token-auth \#认证用的
    --token-auth-file=/opt/kubernetes/cfg/token.csv \#token文件
    --service-node-port-range=30000-50000 \# 端口范围
    --tls-cert-file=/opt/kubernetes/ssl/server.pem  \#配置apiserver证书路径
    --tls-private-key-file=/opt/kubernetes/ssl/server-key.pem \
    --client-ca-file=/opt/kubernetes/ssl/ca.pem \#配置ca证书
    --service-account-key-file=/opt/kubernetes/ssl/ca-key.pem \
    --etcd-cafile=/opt/etcd/ssl/ca.pem \# etcd的ca证书
    --etcd-certfile=/opt/etcd/ssl/server.pem \
    --etcd-keyfile=/opt/etcd/ssl/server-key.pem"
    
    EOF
    # 生成 apiserver.service 配置文件
    cat <<EOF >/usr/lib/systemd/system/kube-apiserver.service
    [Unit]
    Description=Kubernetes API Server
    Documentation=https://github.com/kubernetes/kubernetes
    
    [Service]
    EnvironmentFile=-/opt/kubernetes/cfg/kube-apiserver
    ExecStart=/opt/kubernetes/bin/kube-apiserver $KUBE_APISERVER_OPTS
    Restart=on-failure
    
    [Install]
    WantedBy=multi-user.target
    EOF
    
    systemctl daemon-reload
    systemctl enable kube-apiserver
    systemctl restart kube-apiserver
    
    • 创建日志目录
    mkdir /opt/kubernetes/logs
    
    • 启动apiserver.sh脚本
    ./apiserver.sh 当前master地址 etcd地址
    ./apiserver.sh 192.168.48.128 https://192.168.48.128:2379,https://192.168.48.130:2379,https://192.168.48.131:2379
    
    • 查看apiserver配置文件是否生效
    cat /opt/kubernetes/cfg/kube-apiserver 
    
    • k8s证书生成
    cat > ca-config.json <<EOF
    {
      "signing": {
        "default": {
          "expiry": "87600h"
        },
        "profiles": {
          "kubernetes": {
             "expiry": "87600h",
             "usages": [
                "signing",
                "key encipherment",
                "server auth",
                "client auth"
            ]
          }
        }
      }
    }
    EOF
    
    cat > ca-csr.json <<EOF
    {
        "CN": "kubernetes",
        "key": {
            "algo": "rsa",
            "size": 2048
        },
        "names": [
            {
                "C": "CN",
                "L": "Beijing",
                "ST": "Beijing",
          	    "O": "k8s",
                "OU": "System"
            }
        ]
    }
    EOF
    
    cfssl gencert -initca ca-csr.json | cfssljson -bare ca -
    
    #-----------------------
    
    cat > server-csr.json <<EOF
    {
        "CN": "kubernetes",
        "hosts": [
          "10.0.0.1",
          "127.0.0.1",
          "192.168.48.128",# 这里写master的ip , LB的ip,也可多写几个ip备用,node节点可以不用写
          "192.168.48.129",
          "192.168.48.132",
          "192.168.48.133",
          "192.168.48.134",
          "kubernetes",# 官方默认写入证书
          "kubernetes.default",
          "kubernetes.default.svc",
          "kubernetes.default.svc.cluster",
          "kubernetes.default.svc.cluster.local"
        ],
        "key": {
            "algo": "rsa",
            "size": 2048
        },
        "names": [
            {
                "C": "CN",
                "L": "BeiJing",
                "ST": "BeiJing",
                "O": "k8s",
                "OU": "System"
            }
        ]
    }
    EOF
    
    cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes server-csr.json | cfssljson -bare server
    
    #-----------------------
    # 访问k8s集群时候会用到证书
    cat > admin-csr.json <<EOF
    {
      "CN": "admin",
      "hosts": [],
      "key": {
        "algo": "rsa",
        "size": 2048
      },
      "names": [
        {
          "C": "CN",
          "L": "BeiJing",
          "ST": "BeiJing",
          "O": "system:masters",
          "OU": "System"
        }
      ]
    }
    EOF
    
    cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes admin-csr.json | cfssljson -bare admin
    
    #-----------------------
    # kube-proxy证书
    cat > kube-proxy-csr.json <<EOF
    {
      "CN": "system:kube-proxy",
      "hosts": [],
      "key": {
        "algo": "rsa",
        "size": 2048
      },
      "names": [
        {
          "C": "CN",
          "L": "BeiJing",
          "ST": "BeiJing",
          "O": "k8s",
          "OU": "System"
        }
      ]
    }
    EOF
    
    cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes kube-proxy-csr.json | cfssljson -bare kube-proxy
    
    
    • 执行脚本
    bash k8s-cert.sh
    
    • 将 ca.pem server.pem server-key.pem 放到 /opt/kubernetes/ssl/
    cp ca-key.pem ca.pem server.pem server-key.pem /opt/kubernetes/ssl/
    
    • 生成token
    BOOTSTRAP_TOKEN=0fb61c46f8991b718eb38d27b605b008
    
    cat > token.csv <<EOF
    ${BOOTSTRAP_TOKEN},kubelet-bootstrap,10001,"system:kubelet-bootstrap"
    EOF
    
    • 查看token.csv
    [root@k8s-master01 k8s-cert]# cat token.csv 
    0fb61c46f8991b718eb38d27b605b008,kubelet-bootstrap,10001,"system:kubelet-bootstrap"
          # token的ip				标识用户         #用户组    # 加入k8s角色		
    
    • 移动token.csv 到/opt/kubernetes/cfg/
    mv token.csv /opt/kubernetes/cfg/
    
    • 启动apiserver
    systemctl start kube-apiserver
    
    • 查看是否启动
    ps -ef | grep kube-apiserver
    
    • 若没有正常启动采用手动启动排查问题
    source /opt/kubernetes/cfg/kube-apiserver
    /opt/kubernetes/bin/kube-apiserver $KUBE_APISERVER_OPTS
    

    11.2controller-manager

    • api-server 默认本地监听8080和6443,通过下列指令查看端口

      netstat -antp | grep 8080
      netstat -antp | grep 6443
      
    • 编写脚本controller-manager.sh

    #!/bin/bash
    # api-server的地址
    MASTER_ADDRESS=$1
    
    cat <<EOF >/opt/kubernetes/cfg/kube-controller-manager
    
    
    KUBE_CONTROLLER_MANAGER_OPTS="--logtostderr=true \
    --v=4 \
    --master=${MASTER_ADDRESS}:8080 \# 本地启动ip端口
    --leader-elect=true \
    --address=127.0.0.1 \
    --service-cluster-ip-range=10.0.0.0/24 \
    --cluster-name=kubernetes \
    --cluster-signing-cert-file=/opt/kubernetes/ssl/ca.pem \
    --cluster-signing-key-file=/opt/kubernetes/ssl/ca-key.pem  \
    --root-ca-file=/opt/kubernetes/ssl/ca.pem \
    --service-account-private-key-file=/opt/kubernetes/ssl/ca-key.pem \
    --experimental-cluster-signing-duration=87600h0m0s"
    
    EOF
    
    cat <<EOF >/usr/lib/systemd/system/kube-controller-manager.service
    [Unit]
    Description=Kubernetes Controller Manager
    Documentation=https://github.com/kubernetes/kubernetes
    
    [Service]
    EnvironmentFile=-/opt/kubernetes/cfg/kube-controller-manager
    ExecStart=/opt/kubernetes/bin/kube-controller-manager $KUBE_CONTROLLER_MANAGER_OPTS
    Restart=on-failure
    
    [Install]
    WantedBy=multi-user.target
    EOF
    
    systemctl daemon-reload
    systemctl enable kube-controller-manager
    systemctl restart kube-controller-manager
    
    
    • 运行脚本
    ./controller-manager.sh 本地ip
    ./controller-manager.sh 127.0.0.1
    
    • 验证
    cat /opt/kubernetes/cfg/kube-controller-manager
    
    KUBE_CONTROLLER_MANAGER_OPTS="--logtostderr=true 
    --v=4 
    --master=127.0.0.1:8080 # 本地启动ip端
    --leader-elect=true 
    --address=127.0.0.1 
    --service-cluster-ip-range=10.0.0.0/24 
    --cluster-name=kubernetes 
    --cluster-signing-cert-file=/opt/kubernetes/ssl/ca.pem # 证书签名
    --cluster-signing-key-file=/opt/kubernetes/ssl/ca-key.pem  
    --root-ca-file=/opt/kubernetes/ssl/ca.pem 
    --service-account-private-key-file=/opt/kubernetes/ssl/ca-key.pem 
    --experimental-cluster-signing-duration=87600h0m0s" #为k8s办法证书时间
    
    • 也可将日志文件修改指定文件:
    vi /opt/kubernetes/cfg/kube-controller-manager
    # 修改 --logtostderr=false
    # 修改 --log-dir=指定日志目录,需之前创建好
    

    11.3 scheduler

    • 编写脚本scheduler.sh
    #!/bin/bash
    
    MASTER_ADDRESS=$1
    
    cat <<EOF >/opt/kubernetes/cfg/kube-scheduler
    
    KUBE_SCHEDULER_OPTS="--logtostderr=true \
    --v=4 \
    --master=${MASTER_ADDRESS}:8080 \
    --leader-elect"
    
    EOF
    
    cat <<EOF >/usr/lib/systemd/system/kube-scheduler.service
    [Unit]
    Description=Kubernetes Scheduler
    Documentation=https://github.com/kubernetes/kubernetes
    
    [Service]
    EnvironmentFile=-/opt/kubernetes/cfg/kube-scheduler
    ExecStart=/opt/kubernetes/bin/kube-scheduler $KUBE_SCHEDULER_OPTS
    Restart=on-failure
    
    [Install]
    WantedBy=multi-user.target
    EOF
    
    systemctl daemon-reload
    systemctl enable kube-scheduler
    systemctl restart kube-scheduler
    
    
    • 运行脚本
    ./scheduler.sh 本地ip
    ./scheduler.sh 127.0.0.1
    
    • 验证
    [root@k8s-master01 k8s]# cat /opt/kubernetes/cfg/kube-scheduler 
    
    KUBE_SCHEDULER_OPTS="--logtostderr=true 
    --v=4 
    --master=127.0.0.1:8080 
    --leader-elect"
    

    11.4查看集群状态

    • 将管理k8s集群的工具kubectl拷贝到/usr/bin下方便使用
    cp /root/soft/kubernetes/server/bin/kubectl /usr/bin/
    
    • 查看etcd集群,scheduler,controller-manager状态
    kubectl get cs
    
    NAME                 STATUS    MESSAGE             ERROR
    scheduler            Healthy   ok                  
    controller-manager   Healthy   ok                  
    etcd-2               Healthy   {"health":"true"}   
    etcd-1               Healthy   {"health":"true"}   
    etcd-0               Healthy   {"health":"true"} 
    

    12.k8s 中node部署

    • 在Master中,将之前生成token.csv中定义kubelet-bootstrap用户绑定到系统集群角色
    kubectl create clusterrolebinding kubelet-bootstrap --clusterrole=system:node-bootstrapper --user=kubelet-bootstrap
    
    

    ​ 删除角色名字: kubectl delete clusterrolebinding kubelet-bootstrap [clusterrolebinding kubelet-bootstrap为角色名字]

    • Master创建kubeconfig.sh,生成证书,它用于存放连接apiserver的信息
    APISERVER=$1# 指定apiserver的ip
    SSL_DIR=$2# ssl证书目录
    BOOTSTRAP_TOKEN=0fb61c46f8991b718eb38d27b605b008
    # 创建kubelet bootstrapping kubeconfig 
    export KUBE_APISERVER="https://$APISERVER:6443"
    
    # 设置集群参数
    kubectl config set-cluster kubernetes 
      --certificate-authority=$SSL_DIR/ca.pem 
      --embed-certs=true 
      --server=${KUBE_APISERVER} 
      --kubeconfig=bootstrap.kubeconfig
    
    # 设置客户端认证参数
    kubectl config set-credentials kubelet-bootstrap 
      --token=${BOOTSTRAP_TOKEN} 
      --kubeconfig=bootstrap.kubeconfig
    
    # 设置上下文参数
    kubectl config set-context default 
      --cluster=kubernetes 
      --user=kubelet-bootstrap 
      --kubeconfig=bootstrap.kubeconfig
    
    # 设置默认上下文
    kubectl config use-context default --kubeconfig=bootstrap.kubeconfig
    
    #----------------------
    
    # 创建kube-proxy kubeconfig文件
    
    kubectl config set-cluster kubernetes 
      --certificate-authority=$SSL_DIR/ca.pem 
      --embed-certs=true 
      --server=${KUBE_APISERVER} 
      --kubeconfig=kube-proxy.kubeconfig
    
    kubectl config set-credentials kube-proxy 
      --client-certificate=$SSL_DIR/kube-proxy.pem 
      --client-key=$SSL_DIR/kube-proxy-key.pem 
      --embed-certs=true 
      --kubeconfig=kube-proxy.kubeconfig
    
    kubectl config set-context default 
      --cluster=kubernetes 
      --user=kube-proxy 
      --kubeconfig=kube-proxy.kubeconfig
    
    kubectl config use-context default --kubeconfig=kube-proxy.kubeconfig
    
    • 运行脚本
    bash kubeconfig.sh apiserver的ip ssl证书目录
    
    
    bash kubeconfig.sh 192.168.48.128 /root/k8s/k8s-cert
    
    • 此时生成有2个带kubeconfig后缀文件
    kube-proxy.kubeconfig
    bootstrap.kubeconfig
    
    • 拷贝kube-proxy.kubeconfig, bootstrap.kubeconfig到node节点上
    scp kube-proxy.kubeconfig bootstrap.kubeconfig root@192.168.48.130:/opt/kubernetes/cfg/
    
    scp kube-proxy.kubeconfig bootstrap.kubeconfig root@192.168.48.131:/opt/kubernetes/cfg/
    
    • Master中 /root/soft/kubernetes/server/bin/拷贝kubelet,kube-proxy拷贝到node节点上
    scp kubelet kube-proxy root@192.168.48.130:/opt/kubernetes/bin
    scp kubelet kube-proxy root@192.168.48.131:/opt/kubernetes/bin
    

    kubelet.sh

    • node编写脚本kubelet.sh
    #!/bin/bash
    
    NODE_ADDRESS=$1# 指定当前节点ip
    DNS_SERVER_IP=${2:-"10.0.0.2"}#部署dns使用ip
    # kubelet配置文件
    cat <<EOF >/opt/kubernetes/cfg/kubelet
    
    KUBELET_OPTS="--logtostderr=false \
    --log-dir=/opt/kubernetes/logs \
    --v=4 \
    --address=${NODE_ADDRESS} \
    --hostname-override=${NODE_ADDRESS} \
    --kubeconfig=/opt/kubernetes/cfg/kubelet.kubeconfig \
    --experimental-bootstrap-kubeconfig=/opt/kubernetes/cfg/bootstrap.kubeconfig \
    --config=/opt/kubernetes/cfg/kubelet.config \
    --cert-dir=/opt/kubernetes/ssl \
    --pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0"
    
    EOF
    # kubelet.config 配置文件
    cat <<EOF >/opt/kubernetes/cfg/kubelet.config
    
    kind: KubeletConfiguration
    apiVersion: kubelet.config.k8s.io/v1beta1
    address: ${NODE_ADDRESS}
    port: 10250
    cgroupDriver: cgroupfs
    clusterDNS:
    - ${DNS_SERVER_IP}
    clusterDomain: cluster.local.
    failSwapOn: false
    
    EOF
    # kubelet.service配置文件
    cat <<EOF >/usr/lib/systemd/system/kubelet.service
    [Unit]
    Description=Kubernetes Kubelet
    After=docker.service
    Requires=docker.service
    
    [Service]
    EnvironmentFile=/opt/kubernetes/cfg/kubelet
    ExecStart=/opt/kubernetes/bin/kubelet $KUBELET_OPTS
    Restart=on-failure
    KillMode=process
    
    [Install]
    WantedBy=multi-user.target
    EOF
    
    systemctl daemon-reload
    systemctl enable kubelet
    systemctl restart kubelet
    
    • node执行脚本kubelet.sh
    bash kubelet.sh node节点ip
    
    bash kubelet.sh 192.168.48.130
    
    • 查看配置
    [root@k8s-node01 ~]# cat /opt/kubernetes/cfg/kubelet
    
    KUBELET_OPTS="--logtostderr=false # 指定日志目录
    --log-dir=/opt/kubernetes/logs 
    --v=4 
    --address=192.168.48.130 
    --hostname-override=192.168.48.130 # 当前主机名
    --kubeconfig=/opt/kubernetes/cfg/kubelet.kubeconfig # kubernetes自动生成
    --experimental-bootstrap-kubeconfig=/opt/kubernetes/cfg/bootstrap.kubeconfig #bootstrap
    --config=/opt/kubernetes/cfg/kubelet.config # 配置信息
    --cert-dir=/opt/kubernetes/ssl # 证书
    --pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0"# 创建pod第一个容器引入镜像地址
    
    • 启动异常,可以查看系统日志
    less /opt/kubernetes/logs/kubelet.INFO
    less /var/log/messages -f
    
    • 或是kubelet日志
    journalctl -u kubelet
    
    • 可能出现报错
    failed to run Kubelet: cannot create certificate signing request: certificatesigningrequests.certificates.k8s.io is forbidden: User "kubelet-bootstrap" cannot create resource "certificatesigningrequests" in API group "certificates.k8s.io" at the cluster scope
    
    创建用户绑定到系统集群角色是否正确???
    	删除角色名字: kubectl delete clusterrolebinding kubelet-bootstrap  
    	重新创建:kubectl create clusterrolebinding kubelet-bootstrap --clusterrole=system:node-bootstrapper --user=kubelet-bootstrap
    	# 之前把node-bootstrapper 写成 nodebootstrapper
    
    • 查看启动状态
    systemctl restart kubelet
    
    • 启动成功后会向Master发送请求证书。在Master中执行如下命令查看请求签名
    kubectl get csr
    NAME                                                   AGE     REQUESTOR           CONDITION
    node-csr-Ddql46qTy-HKhQ5ejzeGKpbbugXDII22zTLgRg4hFt0   4m29s   kubelet-bootstrap   Pending
    
    • Master颁发证书
    kubectl certificate approve [NAME]
    kubectl certificate approve node-csr-Ddql46qTy-HKhQ5ejzeGKpbbugXDII22zTLgRg4hFt0
    
    • 查看节点:
    kubectl get node
    

    proxy.sh

    • 编写proxy.sh脚本
    #!/bin/bash
    
    NODE_ADDRESS=$1
    
    cat <<EOF >/opt/kubernetes/cfg/kube-proxy
    
    KUBE_PROXY_OPTS="--logtostderr=true \
    --v=4 \
    --hostname-override=${NODE_ADDRESS} \# 节点名字
    --cluster-cidr=10.0.0.0/24 \# 集群网段
    --proxy-mode=ipvs \# ipvs模式
    --kubeconfig=/opt/kubernetes/cfg/kube-proxy.kubeconfig"
    
    EOF
    
    cat <<EOF >/usr/lib/systemd/system/kube-proxy.service
    [Unit]
    Description=Kubernetes Proxy
    After=network.target
    
    [Service]
    EnvironmentFile=-/opt/kubernetes/cfg/kube-proxy
    ExecStart=/opt/kubernetes/bin/kube-proxy $KUBE_PROXY_OPTS
    Restart=on-failure
    
    [Install]
    WantedBy=multi-user.target
    EOF
    
    systemctl daemon-reload
    systemctl enable kube-proxy
    systemctl restart kube-proxy
    
    • 执行脚本
    bash proxy.sh 192.168.48.130
    
    • 查看启动状态
    ps -ef | grep proxy
    

    node的扩容

    • 从已部署的node拷贝到其他node
    scp -r /opt/kubernetes/ root@192.168.48.131:/opt/
    scp /usr/lib/systemd/system/{kubelet,kube-proxy}.service root@192.168.48.131:/usr/lib/systemd/system/
    
    • 在opt/kubernetes/cfg/目录下修改新node的ip
    vi kubelet
    	--address,--hostname-override 改成本机IP
    vi kubelet.config 
    	address 改成本机IP
    vi vi kube-proxy
    	--hostname-override 改成本机IP
    
    • 删除拷贝过来办法的证书
    cd /opt/kubernetes/ssl/
    rm -f *
    
    • 启动:
    systemctl start kubelet
    systemctl start kube-proxy
    
    • Master颁发证书
    kubectl certificate approve node-csr-gz_UlTpuBdk04IlNgwyC2MfVIhgRDso_xdS3ypGcxzs
    
    • 查看节点
    kubectl get node
    

    13测试一下

    # 创建一个nginx
    kubectl create deployment nginx --image=nginx
    # 暴漏应用 暴漏80端口
    kubectl expose deployment nginx --port=80 --type=NodePort
    [root@k8s-master01 ~]# kubectl get svc
    NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
    kubernetes   ClusterIP   10.0.0.1     <none>        443/TCP        15h
    nginx        NodePort    10.0.0.40    <none>        80:40009/TCP   21m
    
    
    
    • 此时访问子节点 http://192.168.48.131:40009/http://192.168.48.130:40009/就有nginx了

    • 副本扩容

      kubectl scale deployment nignx --replicas=3
      
    • 此时查看kubectl get pods 就会有三个nginx

    14.dashboard可视化部署

    • 先在node的节点上拉取镜像:

      # node 节点拉取镜像
      docker pull lizhenliang/kubernetes-dashboard-amd64:v1.10.1
      
    • 拉取kubernetes-dashboard.yaml

    wget https://raw.githubusercontent.com/kubernetes/dashboard/v1.10.1/src/deploy/recommended/kubernetes-dashboard.yaml
    
    • 修改kubernetes-dashboard.yaml
    vi kubernetes-dashboard.yaml
    # 修改镜像地址
    image: k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.1 改为 image: lizhenliang/kubernetes-dashboard-amd64:v1.10.1
    
    # 在Dashboard Service下spec新增type: NodePor 将其暴漏出来
    # 并在ports新增一个端口  nodePort: 3000
    
    # ------------------- Dashboard Service ------------------- #
    
    kind: Service
    apiVersion: v1
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
      name: kubernetes-dashboard
      namespace: kube-system
    spec:
      type: NodePort
      ports:
        - port: 443
          targetPort: 8443
          nodePort: 30001
      selector:
    
    
    
    • 将修改的kubernetes-dashboard.yaml 应用
    kubectl apply -f kubernetes-dashboard.yaml 
    
    • 查看运行
    kubectl get pods -n kube-system
    
    • 访问页面,因是https请求,它的证书不可信,所以直接跳过:
    https://192.168.48.130:30001/
    
    • 跳过后出现如下页面

    • 创建一个serviceaccount账号

      kubectl create serviceaccount dashboard-admin -n kube-system
      # 名字叫dashboard-admin
      
    • 将创建账号进行权限绑定

      kubectl create clusterrolebinding dashboard-admin --clusterrole=cluster-admin --serviceaccount=kube-system:dashboard-admin
      # dashboard-admin 具有超级管理员的权限
      
    • 这里使用token认证,我们执行命令拿去token

      kubectl describe secrets -n kube-system $(kubectl -n kube-system get secret | awk '/dashboard-admin/{print $1}')
      
    • 展示ui

    • 转载
      转载自:李振良博客

  • 相关阅读:
    标准C的标记化结构初始化语法
    STL中的lower_bound() 和 upper_bound()
    Linux中的file_operation结构
    Linux中进行模块操作的命令
    全球前50大名站
    jQuery实例——选项卡的实现
    我的RHCE之路——RedHat 6 破解grub 恢复grub方法
    PHP获取解析URL方法
    PHP笔试题——遍历文件目录
    PHP面试题——PHP字符串翻转函数
  • 原文地址:https://www.cnblogs.com/xujunkai/p/14533311.html
Copyright © 2011-2022 走看看