zoukankan      html  css  js  c++  java
  • kubeadm 工具部署 kubernetes v1.16.2

    环境准备

    3个节点,以下基于 Centos 7.6 系统, 内核版本:3.10.0-957.12.2.e17.x86_64 

    HOST  NODE CPU MEM
    192.168.1.111 master 最低要求2核2C 最低要求2GB
    192.168.1.112 node1 最低要求2核2C 最低要求2GB
    192.168.1.113 node2 最低要求2核2C 最低要求2GB

    环境初始化-master -node节点都需要操作

    host文件信息:

    cat >>/etc/hosts<<EOF
    192.168.1.111 master
    192.168.1.112 node1
    192.168.1.113 node2
    EOF

    集群免密钥设置:

    yum -y install expect
    ssh-keygen -t rsa -P "" -f /root/.ssh/id_rsa
    for i in 192.168.1.111 192.168.1.112 192.168.1.113;do
    expect -c "
    spawn ssh-copy-id -i /root/.ssh/id_rsa.pub root@$i
        expect {
            "*yes/no*" {send "yes
    "; exp_continue}
            "*password*" {send "Flyaway.123
    "; exp_continue}
            "*Password*" {send "Flyaway.123
    ";}
        } "
    done  

    配置时间同步/关闭防火墙:

    #同步节点时间;
    yum install ntpdate -y
    ntpdate  pool.ntp.org
    
    #临时关闭selinux和防火墙;
    sed -i '/SELINUX/s/enforcing/disabled/g'  /etc/sysconfig/selinux
    setenforce  0
    systemctl   stop     firewalld.service
    systemctl   disable   firewalld.service

    hostname:

    # 节点的 hostname 必须使用标准的 DNS 命名,另外千万不用什么默认的 localhost 的 hostname,会导致各种错误出现的。在 Kubernetes 项目里,机器的名字以及一切存储在 Etcd 中的 API 对象,都必须使用标准的 DNS 命名(RFC 1123)。可以使用命令 hostnamectl set-hostname ydzs-node1 来修改 hostname。
    
    # 修改对应节点主机名;
    hostnamectl set-hostname master &&
    ssh node1 "hostnamectl set-hostname node1" &&
    ssh node2 "hostnamectl set-hostname node2" 
    
    # 拷贝 hosts 文件到 node 节点:
    for i in node1 node2;do scp -r /etc/hosts $i:/etc/ ;done

    由于开启内核 ipv4 转发需要加载 br_netfilter 模块,所以加载下该模块:

     modprobe br_netfilter

    创建 /etc/sysctl.d/k8s.conf 文件,添加如下内容:

    net.bridge.bridge-nf-call-ip6tables = 1
    net.bridge.bridge-nf-call-iptables = 1
    net.ipv4.ip_forward = 1
    
    
    # bridge-nf 解释:
    
    # bridge-nf 使得 netfilter 可以对 Linux 网桥上的 IPv4/ARP/IPv6 包过滤。比如,设置net.bridge.bridge-nf-call-iptables=1后,二层的网桥在转发包时也会被 iptables的 FORWARD 规则所过滤。常用的选项包括:
    # net.bridge.bridge-nf-call-arptables:是否在 arptables 的 FORWARD 中过滤网桥的 ARP 包
    # net.bridge.bridge-nf-call-ip6tables:是否在 ip6tables 链中过滤 IPv6 包
    # net.bridge.bridge-nf-call-iptables:是否在 iptables 链中过滤 IPv4 包
    # net.bridge.bridge-nf-filter-vlan-tagged:是否在 iptables/arptables 中过滤打了 vlan 标签的包。

    执行如下命令使文件生效:

    sysctl -p /etc/sysctl.d/k8s.conf

    安装ipvs:

    $ cat > /etc/sysconfig/modules/ipvs.modules <<EOF
    #!/bin/bash
    modprobe -- ip_vs
    modprobe -- ip_vs_rr
    modprobe -- ip_vs_wrr
    modprobe -- ip_vs_sh
    modprobe -- nf_conntrack_ipv4
    EOF
    
    $ chmod 755 /etc/sysconfig/modules/ipvs.modules && bash /etc/sysconfig/modules/ipvs.modules && lsmod | grep -e ip_vs -e nf_conntrack_ipv4
    
    
    # 安装 ipset  ipvsadm 管理工具(便于查看 ipvs 的代理规则)
     yum install ipset ipvsadm  chrony -y
    
    # 同步服务器时间
    systemctl enable chronyd && systemctl start chronyd && chronyc sources

    关闭 swap 分区:

    $ swapoff -a
    #修改/etc/fstab文件,注释掉 SWAP 的自动挂载,使用free -m确认 swap 已经关闭。swappiness 参数调整,修改/etc/sysctl.d/k8s.conf添加下面一行:
    
    vm.swappiness=0
    #执行sysctl -p /etc/sysctl.d/k8s.conf使修改生效。
    
    

    # 将 k8s.conf 文件 拷贝到 node 节点 for i in node1 node2;do scp -r /etc/sysctl.d/k8s.conf $i:/etc/sysctl.d/ ;done ssh node1 "sysctl -p /etc/sysctl.d/k8s.conf" && ssh node2 "sysctl -p /etc/sysctl.d/k8s.conf"

    部署docker:

    yum install -y yum-utils 
      device-mapper-persistent-data 
      lvm2
    # 如果下面命令执行超时,可以使用阿里云的源代替:http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
    $ yum-config-manager 
        --add-repo 
        https://download.docker.com/linux/centos/docker-ce.repo
    $ yum list docker-ce --showduplicates | sort -r
    
    # 安装 docker-ce:
    yum install docker-ce -y && mkdir /etc/docker
    
    # 配置 docker 镜像加速器:
    
    cat >> /etc/docker/daemon.json<<EOF
    {
      "exec-opts": ["native.cgroupdriver=systemd"],
      "registry-mirrors" : [
        "https://ot2k4d59.mirror.aliyuncs.com/"
      ]
    }
    EOF
    
    # 解释 cgroup 驱动
    
    #由于默认情况下 kubelet 使用的 cgroupdriver 是 systemd,所以需要保持 docker 和kubelet 的 cgroupdriver 一致,我们这里修改 docker 的 cgroupdriver=systemd。如果不修改 docker 则需要修改 kubelet 的启动配置,需要保证两者一致。

    启动 docker:

    for i in start enable status; do systemctl $i docker ;done

    配置 Kubeadm 源:

    # yum 安装 kubeadm 工具时 默认是从 k8s.io 官网下载,对于不能科学**网的来说 比较头疼,所以提供了如下 阿里源 无需科学**网也可进行下载:
    
    #
    可以科学**网 选这个 cat <<EOF > /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64 enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg EOF # 不能科学**网选择这个 cat <<EOF > /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64 enabled=1 gpgcheck=0 repo_gpgcheck=0 gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF

    安装 kubeadm、kubelet、kubectl:

    # --disableexcludes 禁掉除了kubernetes之外的别的仓库
    
    # 安装 v1.16.2 版本:
    yum install -y kubelet-1.16.2 kubeadm-1.16.2 kubectl-1.16.2 --disableexcludes=kubernetes
    kubeadm version
    
    # 设置 kubelet 开机启动:
    systemctl enable --now kubelet

    集群初始化

    在 master 节点配置 kubeadm 初始化文件, 可以通过如下命令导出默认的初始化配置:

    # 为了方便管理 k8s 创建 k8s-install 目录 
    mkdir k8s-install
    
    # 导出初始化配置文件
    cd k8s-install/ kubeadm config print init-defaults > kubeadm.yaml

    修改 kubeadm.yaml文件:

    # 然后根据我们自己的需求修改配置,比如修改 imageRepository 的值,kube-proxy 的模式为 ipvs,另外需要注意的是我们这里是准备安装 flannel 网络插件的,需要将 networking.podSubnet 设置为10.244.0.0/16:
    
    apiVersion: kubeadm.k8s.io/v1beta2
    bootstrapTokens:
    - groups:
      - system:bootstrappers:kubeadm:default-node-token
      token: abcdef.0123456789abcdef
      ttl: 24h0m0s
      usages:
      - signing
      - authentication
    kind: InitConfiguration
    localAPIEndpoint:
      advertiseAddress: 192.168.1.111  # apiserver 节点内网IP
      bindPort: 6443
    nodeRegistration:
      criSocket: /var/run/dockershim.sock
      name: master  # 默认读取当前master节点的hostname
      taints:
      - effect: NoSchedule
        key: node-role.kubernetes.io/master
    ---
    apiServer:
      timeoutForControlPlane: 4m0s
    apiVersion: kubeadm.k8s.io/v1beta2
    certificatesDir: /etc/kubernetes/pki
    clusterName: kubernetes
    controllerManager: {}
    dns:
      type: CoreDNS
    etcd:
      local:
        dataDir: /var/lib/etcd
    imageRepository: registry.aliyuncs.com/google_containers  # 修改成阿里云镜像源
    kind: ClusterConfiguration
    kubernetesVersion: v1.16.2
    networking:
      dnsDomain: cluster.local
      podSubnet: 10.244.0.0/16  # Pod 网段,flannel插件需要使用这个网段
      serviceSubnet: 10.96.0.0/12
    scheduler: {}
    ---
    apiVersion: kubeproxy.config.k8s.io/v1alpha1
    kind: KubeProxyConfiguration
    mode: ipvs  # kube-proxy 模式


    # 配置提示:
    对于上面的资源清单的文档比较杂,要想完整了解上面的资源对象对应的属性,可以查看对应的 godoc 文档,地址: https://godoc.org/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2

    修改配置 kubeadm.yaml 文件后, 进行初始化:

    $ kubeadm init --config kubeadm.yaml
    [preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`
    To see the stack trace of this error execute with --v=5 or higher
    [init] Using Kubernetes version: v1.16.2
    [preflight] Running pre-flight checks
    [preflight] Pulling images required for setting up a Kubernetes cluster
    [preflight] This might take a minute or two, depending on the speed of your internet connection
    [preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
    [kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
    [kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
    [kubelet-start] Activating the kubelet service
    [certs] Using certificateDir folder "/etc/kubernetes/pki"
    [certs] Generating "ca" certificate and key
    x[certs] Generating "apiserver" certificate and key
    [certs] apiserver serving cert is signed for DNS names [ydzs-master kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 10.151.30.11]
    xxx[certs] Generating "apiserver-kubelet-client" certificate and key
    x[certs] Generating "front-proxy-ca" certificate and key
    [certs] Generating "front-proxy-client" certificate and key
    [certs] Generating "etcd/ca" certificate and key
    [certs] Generating "etcd/server" certificate and key
    [certs] etcd/server serving cert is signed for DNS names [ydzs-master localhost] and IPs [10.151.30.11 127.0.0.1 ::1]
    [certs] Generating "etcd/peer" certificate and key
    [certs] etcd/peer serving cert is signed for DNS names [ydzs-master localhost] and IPs [10.151.30.11 127.0.0.1 ::1]
    [certs] Generating "etcd/healthcheck-client" certificate and key
    [certs] Generating "apiserver-etcd-client" certificate and key
    [certs] Generating "sa" key and public key
    [kubeconfig] Using kubeconfig folder "/etc/kubernetes"
    [kubeconfig] Writing "admin.conf" kubeconfig file
    [kubeconfig] Writing "kubelet.conf" kubeconfig file
    [kubeconfig] Writing "controller-manager.conf" kubeconfig file
    [kubeconfig] Writing "scheduler.conf" kubeconfig file
    [control-plane] Using manifest folder "/etc/kubernetes/manifests"
    [control-plane] Creating static Pod manifest for "kube-apiserver"
    [control-plane] Creating static Pod manifest for "kube-controller-manager"
    [control-plane] Creating static Pod manifest for "kube-scheduler"
    [etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
    [wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
    [apiclient] All control plane components are healthy after 39.504262 seconds
    [upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
    [kubelet] Creating a ConfigMap "kubelet-config-1.16" in namespace kube-system with the configuration for the kubelets in the cluster
    [kubelet-check] Initial timeout of 40s passed.
    [upload-certs] Skipping phase. Please see --upload-certs
    [mark-control-plane] Marking the node ydzs-master as control-plane by adding the label "node-role.kubernetes.io/master=''"
    [mark-control-plane] Marking the node ydzs-master as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]
    [bootstrap-token] Using token: abcdef.0123456789abcdef
    [bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
    [bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
    [bootstrap-token] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
    [bootstrap-token] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
    [bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
    [addons] Applied essential addon: CoreDNS
    [addons] Applied essential addon: kube-proxy
    
    Your Kubernetes control-plane has initialized successfully!
    
    To start using your cluster, you need to run the following as a regular user:
    
      mkdir -p $HOME/.kube
      sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
      sudo chown $(id -u):$(id -g) $HOME/.kube/config
    
    You should now deploy a pod network to the cluster.
    Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
      https://kubernetes.io/docs/concepts/cluster-administration/addons/
    
    Then you can join any number of worker nodes by running the following on each as root:
    
    kubeadm join 192.168.1.111:6443 --token abcdef.0123456789abcdef 
        --discovery-token-ca-cert-hash sha256:a292e66049e45264f848186d2fa3582dc360f3b5006cc160f137b5d436e078c2

    拷贝 kubeconfig 文件:

    mkdir -p $HOME/.kube
    sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
    sudo chown $(id -u):$(id -g) $HOME/.kube/config

    kubeadm init 命令执行流程图:

    添加 node 节点

    记住初始化集群上面的配置和操作要提前做好,将 master 节点上面的 $HOME/.kube/config 文件拷贝到 node 节点对应的文件中,安装 kubeadm、kubelet、kubectl(可选),然后执行上面初始化完成后提示的 join 命令即可:

    $ kubeadm join 192.168.1.111:6443 --token abcdef.0123456789abcdef 
    >     --discovery-token-ca-cert-hash sha256:a292e66049e45264f848186d2fa3582dc360f3b5006cc160f137b5d436e078c2
    [preflight] Running pre-flight checks
    [preflight] Reading configuration from the cluster...
    [preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
    [kubelet-start] Downloading configuration for the kubelet from the "kubelet-config-1.16" ConfigMap in the kube-system namespace
    [kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
    [kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
    [kubelet-start] Activating the kubelet service
    [kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...
    
    This node has joined the cluster:
    * Certificate signing request was sent to apiserver and a response was received.
    * The Kubelet was informed of the new secure connection details.
    
    Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

    join 命令:

    如果忘记了上面的 join 命令可以使用 kubeadm token create --print-join-command 重新获取。

    kubeadm join 命令执行流程图:

     添加 node 节点后 查看:

    $ kubectl get nodes
    NAME          STATUS     ROLES    AGE    VERSION
    master        NotReady   master   39m    v1.16.2
    node1         NotReady    none1   106s   v1.16.2
    node1         NotReady    none2   106s   v1.16.2

    网络方案(CNI)两种网络-我们这里选择Flannel  以下作为介绍

    1、Calico 推荐方案,支持网络策略

    为什么部署CNI网络?

    部署CNI是为了不同主机之间的Pod进行通信,因为kubernetes资源调度是需要通信进行计算服务器资源情况进行分配的
    Calico是一个纯三层的数据中心网络方案,Calico支持广泛的平台,包括Kubernetes、OpenStack等。
    Calico 在每一个计算节点利用 Linux Kernel 实现了一个高效的虚拟路由器( vRouter) 来负责数据转发,<br>而每个 vRouter 通过 BGP 协议负责把自己上运行的 workload 的路由信息向整个 Calico 网络内传播。
    此外,Calico  项目还实现了 Kubernetes 网络策略,提供ACL功能

    下载官方默认配置文件

    https://docs.projectcalico.org/manifests/calico.yaml

    下载完后还需要修改里面配置项:

    - 定义Pod网络(CALICO_IPV4POOL_CIDR),与前面初始化集群网络pod CIDR配置一样,后面集群所有的pod都会使用这个网络进行分配地址

    - 选择工作模式(CALICO_IPV4POOL_IPIP),支持**BGP(Never)**、**IPIP(Always)**、**CrossSubnet**(开启BGP并支持跨子网)

    IPIP  隧道模式(overlay网络),基于以太网基础上承载容器数据表
    BGP  纯路由方案,要求在二层网络中,比较推荐的模式(对性能要求高的环境)
    CrossSubnet 跨网段走IPIP,同网段走BGP

    修改完后应用清单和查看状态:

    kubectl apply -f calico.yaml
    kubectl get pods -n kube-system 

    2、Flannel  老版本推荐使用

    Flannel是CoreOS维护的一个网络组件,Flannel为每个Pod提供全局唯一的IP,Flannel使用ETCD来存储Pod子网与Node IP之间的关系。

    flanneld守护进程在每台主机上运行,并负责维护ETCD信息和路由数据包。

    yaml下载地址: (国外下载地址比较慢,可以考虑更换为国内的镜像下载地址,推荐 在阿里源下载)

    https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml  

    可以看到是 NotReady 状态,这是因为还没有安装网络插件,接下来安装网络插件,可以在文档 https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/ 中选择我们自己的网络插件,这里我们安装 flannel:

    $ wget https://raw.githubusercontent.com/coreos/flannel/2140ac876ef134e0ed5af15c65e414cf26827915/Documentation/kube-flannel.yml
    # 因为有节点是多网卡,所以需要在资源清单文件中指定内网网卡
    # 搜索到名为 kube-flannel-ds-amd64 的 DaemonSet,在kube-flannel容器下面
    $ vi kube-flannel.yml
    ......
    containers:
    - name: kube-flannel
      image: quay.io/coreos/flannel:v0.11.0-amd64
      command:
      - /opt/bin/flanneld
      args:
      - --ip-masq
      - --kube-subnet-mgr
      - --iface=eth0  # 如果是多网卡的话,指定内网网卡的名称
    ......
    $ kubectl apply
    -f kube-flannel.yml # 安装 flannel 网络插件

    # 稍等一会查看 Pod 运行状态:
    $ kubectl get pods -n kube-system
    NAME READY STATUS RESTARTS AGE
    coredns-667f964f9b-wb5fn 1/1 Running 0 20m
    coredns-667f964f9b-xmwn2 1/1 Running 0 20m
    etcd-ydzs-master 1/1 Running 0 19m
    kube-apiserver-ydzs-master 1/1 Running 0 19m
    kube-controller-manager-ydzs-master 1/1 Running 0 19m
    kube-flannel-ds-amd64-8l2wr 1/1 Running 0 12m
    kube-flannel-ds-amd64-vwhbh 1/1 Running 0 12m
    kube-proxy-8r4d2 1/1 Running 0 17m
    kube-proxy-rbjv7 1/1 Running 0 20m
    kube-scheduler-ydzs-master 1/1 Running 0 20m

    #
    Flannel 网络插件

    当我们部署完网络插件后执行 ifconfig 命令,正常会看到新增的cni0flannel1这两个虚拟设备,但是如果没有看到cni0这个设备也不用太担心,我们可以观察/var/lib/cni目录是否存在,如果不存在并不是说部署有问题,而是该节点上暂时还没有应用运行,我们只需要在该节点上运行一个 Pod 就可以看到该目录会被创建,并且cni0设备也会被创建出来。
    
    

    Dashboard

    v1.16.2 版本的集群需要安装最新的 2.0+ 版本的 Dashboard:

    # 推荐使用下面这种方式
    $ wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta5/aio/deploy/recommended.yaml
    $ vi recommended.yaml
    # 修改Service为NodePort类型
    ......
    kind: Service
    apiVersion: v1
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
      name: kubernetes-dashboard
      namespace: kubernetes-dashboard
    spec:
      ports:
        - port: 443
          targetPort: 8443
          nodePort: 30001   #手工绑定端口
      selector:
        k8s-app: kubernetes-dashboard
      type: NodePort  # 加上type=NodePort变成NodePort类型的服务
    
    # 创建及查看:
    kubectl apply -f recommended.yaml
    kubectl get pods -n kubernetes-dashboard -l k8s-app=kubernetes-
    kubectl get svc -n kubernetes-dashboard

    登录 Dashboard UI 界面:(注意使用https协议访问,http不可行,master IP+手工绑定的端口,如没有手工绑定端口 会随机生成一个端口)

    https://192.168.1.111:30001

    创建一个具有全局所有权限的用户来登录Dashboard:(admin.yaml) 可选:

    kind: ClusterRoleBinding
    apiVersion: rbac.authorization.k8s.io/v1beta1
    metadata:
      name: admin
      annotations:
        rbac.authorization.kubernetes.io/autoupdate: "true"
    roleRef:
      kind: ClusterRole
      name: cluster-admin
      apiGroup: rbac.authorization.k8s.io
    subjects:
    - kind: ServiceAccount
      name: admin
      namespace: kubernetes-dashboard
    
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: admin
      namespace: kubernetes-dashboard

    创建:

    $ kubectl apply -f admin.yaml
    $ kubectl get secret -n kubernetes-dashboard|grep admin-token
    admin-token-lwmmx                  kubernetes.io/service-account-token   3         1d
    $ kubectl get secret admin-token-lwmmx -o jsonpath={.data.token} -n kubernetes-dashboard |base64 -d# 会生成一串很长的base64后的字符串

    使用上面的 base64 解码后的字符串作为 token 登录 Dashboard 即可,新版本还新增了一个暗黑模式:

    清理:

    如果 集群安装过程中遇到了其他问题,我们可以使用下面的命令来进行重置:

    $ kubeadm reset
    $ ifconfig cni0 down && ip link delete cni0
    $ ifconfig flannel.1 down && ip link delete flannel.1
    $ rm -rf /var/lib/cni/
  • 相关阅读:
    TestNG 入门教程
    Angularjs中文版本开发指南发布
    你是码农 还是优秀程序员?
    java牛人给新人的几点建议
    有一种毒药叫励志书
    牛人眼中如何精通spring?
    驯服你的Windows Server 2003
    windows 7 里面的iis在哪里
    神器 cmder
    亚信UED前端流程自动化构建工具
  • 原文地址:https://www.cnblogs.com/fengyuanfei/p/14013558.html
Copyright © 2011-2022 走看看