zoukankan      html  css  js  c++  java
  • SaltStack系列(四)之实例编写

    前面已经介绍的够多了,这里来让我们写一些完整的实例来梳理一下。

    强调一下,sls文件的抒写格式都是"-"后面跟一个空格,然后后面跟参数: 然后后面再跟一个空格,然后是要填写的值。但是粘贴上面中间空格好多......

    一、初始化实例

    一般一台新机器在交付使用之前,都要做一些初始化优化操作,如调整参数啊,优化服务啊,部署监控啊等,这里我们就写一个相对简单的实例演示一下。

    1.1 服务端的前期准备

    第一步:

    开启自动验证功能(既然我们要自动化,不可能每创建一台新机器就跑到master端去手工认证一次)(两种方式根据自己的情况来)

    第一种:

    # vim /etc/salt/master  #修改master配置文件,开启自动认证。并重启master服务。

    auto_accept: True   #默认是关闭状态

    第二种:

    http://www.51niux.com/?id=120  #关于event和reactor有讲,一种根据监听事件并做相应操作的事情。 

    第二步:

    开启相应的目录设置并重启master服务

    # vim /etc/salt/master  #这里创建了两个环境,一个环境是base环境是默认环境也是必须环境,还有一个prod生产环境。base环境用来部署出初始化操作
    file_roots:
       base:
         - /srv/salt/base

       prod:
          - /srv/salt/prod

    pillar_roots:
      base:
        - /srv/pillar/base
      prod:
        - /srv/pillar/prod

    #file_roots 配置salt配置的存放目录, 其中base环境是必要的, 指定top.sls存放的位置.默认没指定环境时则从base目录获取文件其它则是一些自定义的, 可以通过环境变量指定.这样可以逻辑上隔离一些环境配置.每一个环境都可以定义多个目录, 优先级关系由定义目录的顺序决定.

    第三步:创建对应的目录

    # mkdir -p /srv/salt/base /srv/salt/prod
    # mkdir -p /srv/pillar/base /srv/pillar/prod

    1.2 sls文件的编写

    1.2.1 selinux.sls

    # cat /srv/salt/base/init/selinux.sls  #关闭selinux服务。

    Bash
    selinux:  #ID
      file.replace:    #Function,替换文件内容的方法
        - name: /etc/selinux/config   #在这里是要替换内容的文件
        - pattern: SELINUX=enforcing  #要替换的内容
        - repl: SELINUX=disabled      #替换后的内容
        - count: 1                    #默认为0表示表示所有的都将进行替换,这里加了整数的话就表示替换的次数不超过这个整数
    
    setenforce:
      cmd.run:
        - name: setenforce 0   #这里就是cmd.run后面跟着的要执行的操作。

    #如果有那个方法不知道state怎么写,请:# salt '*' sys.state_doc file 或者# salt '*' sys.state_doc cmd.run 进行查看。

    # salt 'shidc_agent_192.168.1.111' state.sls init.selinux  #如果想测试编写的时候有错误,可以找一台测试机器,单独指定要测试的sls文件进行执行测试。

    图片.png

    1.2.2 repos.sls

    # cat /srv/salt/base/init/repos.sls  #其实这步一般不用做的,因为我们用工具安装操作系统的时候就已经做了,源已经配置好该装的包已经装了。

    Bash
    yum_base_repo_dir:
        cmd.run:
          - name: mv /etc/yum.repos.d /etc/yum.repos.d.bak  #当/etc/yum.repos.d.bak不存在的时候,执行是成功的,再执行就要报错了,所以加下面的条件。
          - unless: test -d /etc/yum.repos.d.bak  #unless是如果条件为false,上面就执行。意思就是如果/etc/yum.repos.d.bak目录存在,上面就不执行mv操作,防止反复执行。
    
      file.directory:  #这是目录创建的方法,正好熟悉一下
        - name: /etc/yum.repos.d  #这是要创建的目录
        - user: root   #目录的用户
        - group: root  #目录的用户组
        - dir_mode: 755  #目录的权限
        - file_mode: 644  #文件的权限
        - makedirs: True   #目录不存在就创建,存在就不会执行创建操作了,这样避免报错,如果不加这个参数,已经创建了目录再执行会报错。
        - recurse:  # 属性权限递归到文件夹内的文件上
          - user  #若本来此目录就存在,且此目录下有很多文件,则recurse函数会把此目录和目录下的所有文件的权限都修改(root 644 755)
          - group
          - mode
          - ignore_files  #忽略文件,还有个 - ignore_dirs忽略目录操作
    
    yum_base_repo_file:
      file.append:  #文件内容的添加,这里有一点要注意,如果下方的内容没有变化,多次执行是不会再次追加的,但是如果下面的四行里面哪一行发生了改变,是会追加到minion端对应的文件底部成为新的一行而不是替换。
        - name: /etc/yum.repos.d/centos-base.repo   #要添加的文件位置
        - makedirs: True   #加上这个参数,如果上一层目录不存在的话,会自动创建,不然如果上层目录不存在的话,会报错。
        - text:  #这是要添加的内容,下面每一行- 跟内容。
          - '[base]'  #这里如果不是'[base]'的形式,如果直接[base]的话,在minion端的文件里面就要变成['base']形式了。
          - name=CentOS-$releasever - Base
          - baseurl=http://yum.51niux.com/centos/$releasever/os/$basearch/
          - gpgcheck=0
    
    yum_epel_repo:   #其实epel这里也应该是file.append,因为相关的rpm包也应该是我们从官网搞的做成的一个yum仓库
      pkg.installed:
       - sources:  #这个参数就是可以执行远程的rpm包的路径
        {% if grains['os'] == 'CentOS' and grains['osmajorrelease'] == '7' %}  #这里就要用到了判断了,如果是Centos7的系统下载rpm包的链接跟Centos6的是不一样的。
          - epel-release: http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-9.noarch.rpm
        {% elif grains['os'] == 'CentOS' and grains['osmajorrelease'] == '6'%}
          - epel-release: http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
        {% endif %}   
       - unless: rpm -qa|grep epel-release  #如果本地没有安装epel就执行,不然又是报错,onlyif:正好跟unless相反,条件为真时执行 。

    图片.png

    #上面是一个多次执行的一台主机效果截图,因为我们基本每一块都加了判断条件,所以不会随便执行,也就不会报错。如果不加各种判断就是下面类似的效果:

    图片.png

    1.2.3 dns.sls

    # cat /srv/salt/base/init/dns.sls  #这步一般也是安装完操作系统就做好了,这里也是选择做的,比如你用的114.114.114.114的域名,又一次就出问题了好几天这时候你就需要统一更换dns服务器保证解析的正常,当然如果用的是内网DNS服务的话,这里就没必要搞了,直接再DNS服务器那里调整就可以了。

    Bash
    /etc/resolv.conf:  #这里没有设置name,所以/etc/resolv.conf既是ID:又是name,就是我们要操作的文件。
      file.managed:  #这里引用的是file.managed方法
        - source: salt://init/files/resolv.conf  #下载的文件的源位置。在根目录下的init/files目录下。
        - user: root
        - group: root
        - mode: 0644

    # cat /srv/salt/base/init/files/resolv.conf  #这是要发送的resolv.conf文件

    Bash
    nameserver 223.5.5.5
    nameserver 223.6.6.6

    1.2.4 history.sls

    # cat /srv/salt/base/init/history.sls #这个也是挺有用的记录时间记录用户做了什么操作

    Bash
    /etc/profile:
      file.append:
        - text:
          - export HISTTIMEFORMAT="%F %T `whoami` "  #只能是这种export生效的方式了,当然新的窗口就能看到效果了,不能指望着source /etc/profile,可以用cmd.run跑一下都不支持......

    1.2.5 sysctl.sls

    # cat /srv/salt/base/init/sysctl.sls  #内核的参数优化也是要做的。

    Bash
    net.ipv4.ip_local_port_range:  #格式比较简单,这里是既是ID:又是name,就相当于name: net.ipv4.ip_local_port_range
      sysctl.present:    #格式就是在这个下面,指定name就是哪个参数要修改
        - value: 10000 65000  #- value: 后面指定要修改的值,更新/etc/sysctl.conf文件。并立即生效的,类似于#sysctl -p
    fs.file-max:
      sysctl.present:
        - value: 655350
    vm.swappiness:
      sysctl.present:
        - value: 0
    net.core.rmem_max:
      sysctl.present:
        - value: 33554432
    net.core.wmem_max:
      sysctl.present:
        - value: 33554432

    1.2.6 service.sls

    # cat /srv/salt/base/init/service.sls  #service初始化也是要做的,关闭不必要的服务,按理说Centos7和Centos6的应该用个if区分开,我这就都放一起了偷个懒。

    Bash
    {% set service_list = ['firewalld','iptables','ip6tables','NetworkManager','nfs-config','postfix','auditd','cups','cupsd'] %}  #由于service的格式限制,我这就用了个for循环来避免不必要的编写。
    {% for num in service_list %}
    {{ num }}:
        service.running:
           - enable: False
        cmd.run:
           - name: service {{num}} stop
    {% endfor %}

    1.2.7 其他文件配置并查看

    # cat /srv/salt/base/init/env_init.sls

    Bash
    include:   #include多sls文件可以以列表形式,用下面这种形式。
      - init.dns  #指定init目录下面的dns.sls文件,.代表下一级目录
      - init.history
      - init.sysctl
      - init.repos
      - init.selinux
       -  init.service

    # cat /srv/salt/base/top.sls #在入口文件top.sls里面给minion指定状态并执行init目录下的env_init.sls文件

    Bash
    base:
      '*':
         - init.env_init

    # salt '*' state.highstate test=True  #服务端执行,可以先加test,这样出问题不执行也能提前解决一下,安全性高一点。

    # salt '*' state.highstate  #让所有的minion端同步状态,从top.sls进入一看,一看就是执行init.env_init这个配置。

    如果是客户端的操作的话就是:

    #salt-call state.highstate  #手工执行

    #salt-call state.sls init.env_init  #或者可以手工指定sls文件执行。

    博文来自:www.51niux.com

    二、lnmp实例

    2.1  创建必要目录

    # mkdir -p /srv/salt/prod/pcre/files

    # mkdir -p /srv/salt/prod/nginx/files

    # mkdir -p /srv/salt/prod/php/files
    # mkdir -p /srv/salt/prod/mysql/files
    # mkdir -p /srv/salt/prod/pkg/files

    # mkdir -p /srv/salt/prod/user

    2.2 编写pkg包文件

    # cat /srv/salt/prod/pkg/pkg-init.sls  #先提前yum安装必要的软件包

    Bash
    pkg-init:  #这是两种安装方式,一种是将要安装的软件包做成下面列表的形式,这样包组的之间的安装基本不会有影响。
      pkg.installed:
        - names:
           - bison
           - gcc
           - gcc-c++
           - autoconf
           - automake
           - zlib
           - libxml2
           - ncurses-devel
           - cmake
           {% if grains['os'] == 'CentOS' and grains['osmajorrelease'] == '7' %}
           - mariadb-devel 
           {% elif grains['os'] == 'CentOS' and grains['osmajorrelease'] == '6'%}
           - mysql-devel
           {% endif %}
    pkg-yum:    #这是另外一种通过cmd.run执行yum安装命令的操作,这种对于要安装的软件包组的操作比较方便,但是如果有一个软件包安装出现问题将导致整个过程都失败,所以要提前测试好并yum源仓库做好配置。
      cmd.run:
         - name: yum install openssl* libjpeg libjpeg-devel libpng libpng-devel freetype freetype-devel libxml2 libxml2-devel zlib zlib-devel ncurses ncurses-devel curl curl-devel gd gd2 gd-devel gd2-devel zlib libxml libjpeg freetype libpng gd curl libiconv zlib-devel libxml2-devel libjpeg-devel freetype-devel libpng-devel gd-devel curl-devel libxslt-devel -y

    # salt 'tjidc_agent_192.168.1.102_youshi' state.sls saltenv='prod' pkg.pkg-init test=True 或者# salt 'tjidc_agent_192.168.1.102_youshi' state.sls saltenv='prod' pkg.pkg-init  #指定一台机器测试一下。

    #注意上面的格式,state.sls saltenv='prod' 这是针对多环境,因为我们还没有再top.sls定义,所以不能用那个 state.highstate命令。state.sls 并不读取 top.sls,现在我们的sls文件在prod环境下并非在base环境下,所以要加上指定环境saltenv的参数,然后里面指定了prod环境。然后后面跟的是哪个目录下的哪个sls文件,就跟之前上面测试单文件没有什么差别了。

    2.3 user用户编写 

    # cat /srv/salt/prod/user/www.sls  #指定一个www用户

    Bash
    www-user-group:   #ID:
      group.present:  #调用的是创建组的函数
         - name: www   #组名称
         - gid: 500    #gid设置
      user.present:
         - name: www   #用户名
         - fullname: www  
         - shell: /sbin/nologin  #用户不登录
         - uid: 500   #uid
         - gid: 500   #gid

    # cat /srv/salt/prod/user/mysql.sls  #创建一个MySQL的用户组和用户

    Bash
    mysql-user-group:
      group.present:
         - name: mysql
         - gid: 600
      user.present:
         - name: mysql
         - fullname: mysql
          -  gid_from_name: True  #其实一般可以不用创建组的,直接在这里加上这么一句话,默认是False。就是让组id也跟着创建并且gid跟uid一致。
         - shell: /sbin/nologin
         - uid: 600
         - gid: 600

    2.4 mysql安装

    # cat /srv/salt/prod/mysql/install.sls

    Bash
    include:
       - pkg.pkg-init
       - user.mysql
    cmake-install:
      file.managed:
        - name: /opt/cmake-2.8.6.tar.gz
        - source: salt://mysql/files/cmake-2.8.6.tar.gz
      cmd.run:
        - name: cd /opt/ && tar zxf cmake-2.8.6.tar.gz && cd cmake-2.8.6 && ./configure && make && make install
    mysql-install:
      file.managed:
        - name: /opt/mysql-5.5.35.tar.gz
        - source: salt://mysql/files/mysql-5.5.35.tar.gz
        - user: root
        - group: root
        - mode: 644
      cmd.run:
        - name: cd /opt/ && tar zxf mysql-5.5.35.tar.gz && cd mysql-5.5.35 && /usr/local/bin/cmake -DCMAKE_INSTALL_PREFIX=/usr/local/mysql-5.5.35 -DMYSQL_UNIX_ADDR=/tmp/mysql.sock -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci -DEXTRA_CHARSETS=all -DWITH_MYISAM_STORAGE_ENGINE=1 -DWITH_INNOBASE_STORAGE_ENGINE=1 -DWITH_READLINE=1 -DENABLED_LOCAL_INFILE=1 -DMYSQL_DATADIR=/mysqldata -DMYSQL_TCP_PORT=3306 && make && make install && chown -R mysql:mysql /usr/local/mysql-5.5.35 && ln -s /usr/local/mysql-5.5.35  /usr/local/mysql && cp support-files/mysql.server  /etc/init.d/mysqld && cp support-files/my-small.cnf /etc/my.cnf && chmod 755 /etc/init.d/mysqld && chown mysql:mysql /etc/my.cnf && mkdir /mysqldata && chown mysql:mysql /mysqldata && /usr/local/mysql/scripts/mysql_install_db --defaults-file=/etc/my.cnf --basedir=/usr/local/mysql --datadir=/mysqldata
        - unless: test -d /usr/local/mysql
        - require:  #因为头部已经加载了,所以这里可以直接引用文件里面定义的内容
          - user: mysql-user-group   #多行就是这种形式用- 隔开
          - pkg: pkg-init
      /etc/profile:
           file.append:
             - text:
                - export PATH=$PATH:/usr/local/mysql/bin

    2.5 pcre安装

    # cat /srv/salt/prod/pcre/install.sls

    Bash
    pcre-install:
       file.managed:
         - name: /opt/pcre-8.40.tar.gz
         - source: salt://pcre/files/pcre-8.40.tar.gz  #因为我们已经在master定义了prod的files根路径,所以这里/srv/salt/prod是根路径
         - user: root
         - group: root
         - mode: 644
       cmd.run:
         - name: cd /opt/ && tar zxf pcre-8.40.tar.gz && cd pcre-8.40 && ./configure --prefix=/usr/local/pcre && make && make install
         - unless: test -d /usr/local/pcre  #这里还是为了防止反复安装
         - require:
           - file: pcre-install  #首先得把pcre.tar.gz传过来才能安装嘛

    # salt 'tjidc_agent_192.168.1.102_youshi' state.sls saltenv='prod' pcre.install -v  #可以找一台测试机执行一下并加-v看一看详细的过程。

    2.6 pillar设置

    # cat /srv/pillar/prod/top.sls  #要有这个top.sls文件,在里面设置一下环境和引用哪个文件。不然使用pillar的时候会找不到。

    prod:   #我们定义的prod环境
      '*':     #这里应该加个过滤的,什么样的主机才能引用下面的service.sls,如web服务器啊啥的
         - service  #这里指定了引用哪个文件

    # cat /srv/pillar/prod/service.sls #简单设置了一个nginx引用的pillar数据文件,里面可以用if来判断的。

    nginx:   #这里相当于字典的key是要被调用的
      port: 80  #port也是一个key,而80是值。
      user: www

    2.7 nginx安装

    # cat /srv/salt/prod/nginx/install.sls 

    Bash
    include:
      - pcre.install
      - user.www
    nginx-install:
      file.managed:
        - name: /opt/nginx-1.0.6.tar.gz
        - source: salt://nginx/files/nginx-1.0.6.tar.gz
        - user: root
        - group: root
        - mode: 644
      cmd.run:
        - name : cd /opt/ && tar zxf nginx-1.0.6.tar.gz && cd nginx-1.0.6 && ./configure --user=www --group=www --prefix=/usr/local/nginx-1.0.6 --with-http_stub_status_module --with-http_ssl_module && make && make install && echo "/usr/local/lib" >> /etc/ld.so.conf &&ln -s /usr/local/nginx-1.0.6 /usr/local/nginx 
        - unless: test -d /usr/local/nginx   #出现unless的地方都是为了防止重复安装
        - require:
          - user: www-user-group
          - file: nginx-install

    # cat /srv/salt/prod/nginx/service.sls

    Bash
    include:
      - nginx.install
    nginx-config:
      file.managed:
        - name: /usr/local/nginx/conf/nginx.conf
        - source: salt://nginx/files/nginx.conf.jinja  #这里就需要调用jinja模板了,根据客户机的不同设置不同的参数
        - template: jinja
        - user: www
        - group: www
        - mode: 644
        - defaults:   #这里一共用了四个变量,第一个用户用的是/srv/pillar/prod/service.sls文件下面ID:是nginx下面的user对应的值,也就是www。
          Web_User: {{ pillar['nginx']['user'] }}    
          Nginx_Port: {{ pillar['nginx']['port'] }}  #这个就是对应的port: 80,也就是Nginx_Port被传参80.
          Cpu_Num: {{ grains['num_cpus'] }}  #这里用到了grains,也就是客户机传送上来的本机信息中的CPU数量。
          IP: {{ grains['ip_interfaces']['eth0'][0]}}  #这里一定要用[0]来取一下,万一你获取的是多个值,这里就要是这种形式了:server_name  ['192.168.1.112', 'fe80::20c:29ff:fea8:3398'];
        - unless: test -d /usr/local/nginx  #防止反复编写nginx.conf文件
    nginx-service:
      cmd.run:
        - name: /usr/local/nginx/sbin/nginx
        - unless: test -f /usr/local/nginx/logs/nginx.pid  #防止反复启动nginx,上面的命令不是重启操作,如果这里不指定可能会报错。

    #  cat /srv/salt/prod/nginx/files/nginx.conf.jinja |grep '{{'   #查看一下我们上面设置的那四个变量都对应着配置文件中那四个参数

    Bash
    user  {{Web_User}};
    worker_processes {{Cpu_Num}} ;
            listen       {{Nginx_Port}};
            server_name  {{IP}};

    # salt 'shidc_agent_192.168.1.111' state.sls saltenv='prod' nginx.service test=True  #可以找一台测试机测试执行一下看下有没有报错,内容太多就不截图了。 

    图片.png

    2.8 php安装

    php安装就不记录了,写也是照着上面的重复的来,好多该介绍和举例的地方上面已经介绍完毕了,php就是照着安装过程文档改成salt的安装方式。

    博文来自:www.51niux.com

    2.9 总体调用执行

    上面已经记录了如何单独的去测试执行。编写测试的时候倒是可以这样,但是实际使用要简单就部署了,所以还需要一些设计和调用。

    第一种方式(这样做可能自动化稍弱一点人工干预的动作做一点,但是相对复杂度和对整体设计的要求要低一点):

    # cat /srv/salt/prod/web_lnmp.sls  #如做成这种,每个软件的安装都是一个单独的目录,然后写一个sls针对不同的业务来调不同的软件。

    include:
      - mysql.install
      - pcre.install
      - nginx.install

    # salt 'shidc_agent_192.168.1.111' state.sls saltenv='prod' web_lnmp  #如我们就可以制定某一台机器或者之前不是说过可以设置组还有各种筛选条件来控制让哪些主机来按照我们指定的的方式去操作执行。这样就可以实现控制多机去部署不同的业务。

    # salt-call state.sls saltenv='prod'  web_lnmp #当然也可以在minion机上面执行命令,也能很快的部署业务。如是mfs的部署,你就可以把web_lnmp改成data_hadoop。

    第二种方式(这种方式首先是要对主机有一个很合理的规划设计,然后再文件编写方面要加一些判断):

    # cat /srv/salt/prod/top.sls

    Bash
    prod:
       'nodename:*_web_*_youshi':  #这里千万不能再指'*'了。这里是根据grain信息里的主机名称进行匹配,让所有的中间带web的以youshi结尾的去执行web_lnmp
          - match: grain
          - web_lnmp

    注:因为前面已经提过了top.sls是入口文件,那么如果这个地方你设置的是'*'而不是其他的过滤条件,然后不管是minion端还是master端只要一运行state.highstate,那么不仅init下面的初始化的env_init.sls会运行,连prod下面的这个web_lnmp也会运行,这就相当于所有的minion端都会部署lnmp环境,以后如果再有别的环境也是要部署进来的,那么久不好玩了。

    当然你可以就只留下init下面的一个env_init.sls放到top.sls里面,其他的都用state.sls的方式手工指定,当然也是可以的。

    但是如果我们想就是采取服务端执行:# salt '*' state.highstate test=True或者客户端执行:salt-call state.highstate。不管是初始优化还是我所需要该安装的软件该配置的文件该启动的服务都给我做好呢?

    就是我上面配置语言那种方式,前面我们已经说到过了,关于要去执行的目标我们有多种的匹配方式,而且支持正则表达式。我们就在我们要执行的sls上面加好过滤条件,设置的精准严格一点,这样就不会出现一运行state.highstate,所有的节点不管是不是web服务器或者别的什么,都把所有的软件装一遍。

    如下面的小例子:

    # salt '*' grains.items|grep nodename -A 1

    图片.png

    #从上如中可以看出我又两种不同业务的主机,然后主机名是固定格式,在第二部分会标注他们的业务属性。我上面的的top.sls简单示例就是根据主机名中所体现的业务出行做的过滤。如果匹配不上就不会执行web_lnmp,至少能保证大部分的minion服务器除了执行初始优化以外不会再去执行其他的管理操作。

    # salt '*' state.highstate test=True  

    #可以执行测试看一下,其实在实际生产中,这里在执行的时候也是有匹配条件的,有些主机还会做成组,所以说这种第二种方式,自动化更高,不过需要我们在sls文件编写方面要更加严格和精准,并且在top.sls里面的匹配一定要是正确的,不然会造成有的主机明明是这个业务,但是因为没有匹配上而造成没有进行必要的管理配置。

    # hostname   #首先在一台web机器上面查看我们的匹配条件设置的是否成功。
    shidc_web_192.168.1.111_youshi

    # salt-call state.highstate  #然后执行客户端拉拉取top.sls并执行配置管理的操作

    图片.png

    图片.png

    #内容太多就不全部截图了,从这两张图就可以看出来,192.168.1.111这台机器因为主机名里面有web,所以分别从base和prod里面都同步了top.sls文件,并且不仅进行了初始化的管理也进行了web_lnmp的配置。

    # hostname  #在看下另外一台主机是cache业务的主机。
    zwidc_cache_192.168.1.112_youshi

    # salt-call state.highstate  #执行同步操作

    图片.png

    图片.png

    #从上图中可以看出,192.168.1.112这台机器,虽然两个top.sls文件都同步了,但是因为其主机名所带业务特征里面是cache而非web,所以只执行了初始化env_init的操作,并没有执行web_lnmp的操作。

  • 相关阅读:
    笔记44 Hibernate快速入门(一)
    tomcat 启用https协议
    笔记43 Spring Security简介
    笔记43 Spring Web Flow——订购披萨应用详解
    笔记42 Spring Web Flow——Demo(2)
    笔记41 Spring Web Flow——Demo
    Perfect Squares
    Factorial Trailing Zeroes
    Excel Sheet Column Title
    Excel Sheet Column Number
  • 原文地址:https://www.cnblogs.com/wangshuyang/p/9055424.html
Copyright © 2011-2022 走看看