1.png

前言

Tomcat是一个功能强大、易于使用的Java应用服务器,适用于各种规模的Web应用程序开发和部署需求。今天我们将探讨如何将Tomcat服务迁移到云端,以及在上云过程中可能遇到的问题及解决方案。

Tomcat是一个开源的Java Servlet容器,也是一个流行的Java应用服务器。它由Apache软件基金会开发和维护,是一个轻量级、高性能的Web服务器,用于部署和运行Java Web应用程序。主要有以下特点:

  1. 开源免费:Tomcat是一个开源项目,可以免费使用和修改。
  2. Java Servlet容器:Tomcat是一个Java Servlet容器,它实现了Java Servlet和JavaServer Pages(JSP)规范,可以运行基于这些技术的Web应用程序。
  3. 轻量级:Tomcat是一个轻量级的Web服务器,它的核心组件相对较小,占用的系统资源较少。
  4. 可扩展性:Tomcat提供了丰富的扩展机制,可以通过插件和扩展来增强其功能,例如添加新的协议、连接池、安全性等。
  5. 高性能:Tomcat采用了多线程处理请求的方式,可以同时处理多个请求,提供较高的并发性能。
  6. 易于部署和管理:Tomcat提供了简单易用的管理界面,可以方便地部署、配置和管理Web应用程序。
  7. 跨平台:Tomcat可以在多个操作系统上运行,包括Windows、Linux、Mac等。

Tomcat广泛应用于Java Web开发和部署领域,常见的应用场景包括:

  • Web应用程序开发:Tomcat提供了一个运行Java Web应用程序的环境,开发人员可以使用Java Servlet和JSP等技术来构建动态的Web应用程序。

  • 企业级应用程序:Tomcat可以作为企业级应用服务器的一部分,用于部署和运行Java EE(Enterprise Edition)应用程序,如Java EE容器、EJB(Enterprise JavaBeans)等。

  • 测试和开发环境:Tomcat可以用作测试和开发环境,开发人员可以在本地机器上快速部署和调试Web应用程序。

  • 教育和培训:Tomcat作为一个开源、易于使用的Java应用服务器,常用于教育和培训领域,帮助学习者理解和学习Java Web开发的基本概念和技术。

云端部署过程

步骤1:创建Tomcat Docker镜像

首先,我们需要创建一个Docker镜像,其中包含了Tomcat服务器和我们的应用程序。可以使用以下Dockerfile来构建镜像:

FROM tomcat:latest
COPY ./webapp.war /usr/local/tomcat/webapps/

在上述Dockerfile中,我们使用了官方的Tomcat镜像作为基础镜像,并将我们的应用程序(webapp.war)复制到Tomcat的webapps目录下。

步骤2:创建server.xml配置文件

接下来,我们需要创建一个server.xml配置文件,用于自定义Tomcat的配置。可以使用以下示例文件作为参考:

<?xml version='1.0' encoding='utf-8'?>
<!--
  Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, Version 2.0
  (the "License"); you may not use this file except in compliance with
  the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
-->
<!-- Note:  A "Server" is not itself a "Container", so you may not
     define subcomponents such as "Valves" at this level.
     Documentation at /docs/config/server.html
 -->
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <!-- Security listener. Documentation at /docs/config/listeners.html
  <Listener className="org.apache.catalina.security.SecurityListener" />
  -->
  <!--APR library loader. Documentation at /docs/apr.html -->
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <!-- Prevent memory leaks due to use of particular java/javax APIs-->
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <!-- Global JNDI resources
       Documentation at /docs/jndi-resources-howto.html
  -->
  <GlobalNamingResources>
    <!-- Editable user database that can also be used by
         UserDatabaseRealm to authenticate users
    -->
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <!-- A "Service" is a collection of one or more "Connectors" that share
       a single "Container" Note:  A "Service" is not itself a "Container",
       so you may not define subcomponents such as "Valves" at this level.
       Documentation at /docs/config/service.html
   -->
  <Service name="Catalina">

    <!--The connectors can use a shared executor, you can define one or more named thread pools-->
  
    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
        maxThreads="600" minSpareThreads="50" maxQueueSize="100" maxIdleTime="30000" prestartminSpareThreads="true" />
    
    <!-- A "Connector" represents an endpoint by which requests are received
         and responses are returned. Documentation at :
         Java HTTP Connector: /docs/config/http.html (blocking & non-blocking)
         Java AJP  Connector: /docs/config/ajp.html
         APR (HTTP/AJP) Connector: /docs/apr.html
         Define a non-SSL/TLS HTTP/1.1 Connector on port 8080
    -->
      <!--端口修改为8088-->
 <Connector port="8088" protocol="HTTP/1.1" connectionTimeout="20000" server="MyTomcat" xpoweredBy="false" relaxedPathChars='[]|' relaxedQueryChars='[]|{}^&#x5c;&#x60;&quot;&lt;&gt;' maxThreads="600" minSpareThreads="50" maxSpareThreads="50" acceptCount="5000"/>
      
<!-- 
如果启用https,使用这个8088的配置,注释掉上面的8088配置
 <Connector port="8088" protocol="HTTP/1.1" connectionTimeout="20000" server="MyTomcat" xpoweredBy="false" relaxedPathChars='[]|' relaxedQueryChars='[]|{}^&#x5c;&#x60;&quot;&lt;&gt;' redirectPort="8443" maxThreads="600" minSpareThreads="50" maxSpareThreads="50" acceptCount="5000"/>

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
      SSLEnabled="true" scheme="https" secure="true" enableLookups="false"  clientAuth="false" sslProtocol="TLS" sslEnabledProtocols="TLSv1,TLSv1.1,TLSv1.2,SSLv2Hello" 
      keystoreFile="/XXX/XXX.keystore" keystorePass="XXX"  maxThreads="600" minSpareThreads="50" maxSpareThreads="50" />
  -->  
<!-- A "Connector" using the shared thread pool-->
    <!--
    <Connector executor="tomcatThreadPool"
               port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    -->
    <!-- Define a SSL/TLS HTTP/1.1 Connector on port 8443
         This connector uses the NIO implementation that requires the JSSE
         style configuration. When using the APR/native implementation, the
         OpenSSL style configuration is required as described in the APR/native
         documentation -->
    <!--
    <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
               clientAuth="false" sslProtocol="TLS" />
    -->

    <!-- Define an AJP 1.3 Connector on port 8009 -->

    <!-- An Engine represents the entry point (within Catalina) that processes
         every request.  The Engine implementation for Tomcat stand alone
         analyzes the HTTP headers included with the request, and passes them
         on to the appropriate Host (virtual host).
         Documentation at /docs/config/engine.html -->

    <!-- You should set jvmRoute to support load-balancing via AJP ie :
    <Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
    -->
    <Engine name="Catalina" defaultHost="localhost">

      <!--For clustering, please take a look at documentation at:
          /docs/cluster-howto.html  (simple how to)
          /docs/config/cluster.html (reference documentation) -->
      <!--
      <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
      -->

      <!-- Use the LockOutRealm to prevent attempts to guess user passwords
           via a brute-force attack -->
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <!-- This Realm uses the UserDatabase configured in the global JNDI
             resources under the key "UserDatabase".  Any edits
             that are performed against this UserDatabase are immediately
             available for use by the Realm.  -->
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">

        <!-- SingleSignOn valve, share authentication between web applications
             Documentation at: /docs/config/valve.html -->
        <!--
        <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
        -->
         <!-- 核心部分 -->
         <Valve className="org.apache.catalina.valves.ErrorReportValve" showReport="false" showServerInfo="false" />
		<Valve className="org.apache.catalina.valves.RemoteIpValve" remoteIpHeader="X-Real-IP" />
		<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t %r %s %b" prefix="localhost_access_log" suffix=".txt" />
        <!-- Access log processes all example.
             Documentation at: /docs/config/valve.html
             Note: The pattern used is equivalent to using pattern="common" -->

      </Host>
    </Engine>
  </Service>
</Server>

在上述示例文件中,我们将默认的8080端口修改为了8088端口。同时,我们配置了允许某些特殊字符可以通过GET方式进行请求,并对Tomcat的默认线程池配置进行了优化。此外,在<Host>标签内,我们对Tomcat报错信息和版本号信息进行了隐藏处理。另外,我们还配置了X-Forwarded-For头部以获取客户端的真实IP地址。

步骤3:创建ConfigMap

我们将使用ConfigMap来存储server.xml配置文件。可以使用以下命令创建ConfigMap:

kubectl create configmap tomcat-config --from-file=server.xml

上述命令将创建一个名为tomcat-config的ConfigMap,并将当前目录下的server.xml文件添加到ConfigMap中。

步骤4:创建k8s部署文件

接下来,我们需要创建一个k8s部署文件,用于定义Tomcat服务的部署和管理,并使用NodePort方式将服务暴露给外部访问。可以使用以下示例文件作为参考:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: tomcat-deployment
spec:
  replicas: 3  
  selector:
    matchLabels:
      app: tomcat
  template:
    metadata:
      labels:
        app: tomcat
    spec:
      containers:
      - name: tomcat
        image: your-docker-registry/tomcat-image:latest
        ports:
        - containerPort: 8088
        env:
        - name: TZ
          value: "Asia/Shanghai"
        volumeMounts:
        - name: config-volume
          mountPath: /usr/local/tomcat/conf/server.xml
          subPath: server.xml
      volumes:
      - name: config-volume
        configMap:
          name: tomcat-config
---
apiVersion: v1
kind: Service
metadata:
  name: tomcat-service
spec:
  type: NodePort
  selector:
    app: tomcat
  ports:
    - protocol: TCP
      port: 8088
      targetPort: 8088
      nodePort: 30000

在上述示例文件中,我们添加了一个volumeMountsvolumes字段,用于挂载ConfigMap中的server.xml配置文件到Tomcat容器的/usr/local/tomcat/conf/server.xml路径下。我们还通过设置环境变量TZAsia/Shanghai解决时区问题,确保Tomcat服务器使用正确的时区。

步骤5:部署Tomcat服务

使用kubectl命令行工具,执行以下命令来部署Tomcat服务:

kubectl apply -f tomcat-deployment.yaml

上述命令将根据我们的部署文件,在k8s集群中创建Tomcat服务。

步骤6:访问Tomcat应用程序

一旦Tomcat服务成功部署,我们可以使用以下命令来获取Tomcat服务的访问地址:

kubectl get nodes -o wide

在输出结果中,找到任意一个节点的IP地址。使用浏览器或curl命令,访问http://<Node IP>:<NodePort>/webapp,其中<Node IP>为节点的IP地址,<NodePort>为之前定义的NodePort(例如30000)。

步骤7:解决其他问题

经过上述六个步骤,我们已经成功搭建了一个可用的Tomcat服务。对于大部分应用来说,这已经足够满足需求。然而,针对项目的特殊要求,我们仍需进行额外的配置工作。

上云后如何获取客户端真实IP:

我们需要在service.yaml中配置外部流量策略为Local

spec:
  externalTrafficPolicy: Local

然而,当将配置设置为Local时,可能会出现负载不均衡的问题。为了解决这个问题,我们需要在deploy.yaml文件中对pod进行反亲和性配置。

spec:
  template:
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - topologyKey: kubernetes.io/hostname
            labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                  - tomcat

注意:由于外部流量策略配置为Local,当使用service的NodePort模式直接对外提供服务时,只能通过该pod所在的宿主机的IP进行访问。然而,如果使用域名进行访问,域名代理的IP将是所有能够部署该服务的所有宿主机的IP。因此,在后续增加机器的情况下,域名代理IP也需要相应地增加。

图片验证码问题:

image-20230302103919895

在构建 Dockerfile 时,我们通常会选择体积较小的 slim 版本镜像。然而,由于缺少某些必要组件,可能会出现上图所示的问题。为了解决这个问题,我们应该选择完整的 openjdk 版本镜像进行构建。

小结

通过以上步骤,我们成功的在k8s上部署和管理了Tomcat服务,并使用NodePort方式将服务暴露给外部访问。同时,我们使用ConfigMap来挂载Tomcat的server.xml配置文件,以便进行自定义配置。这使得Tomcat应用程序在k8s集群中更加灵活和可定制。使用k8s的优势是可以轻松地扩展和管理应用程序,同时提供高可用性和容错能力,这使得Tomcat应用程序在生产环境中更加稳定和可靠。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐