12 Commits

Author SHA1 Message Date
gomdobi 432c54b58e Enable Redis sessions for prod validation
Main-Build / build-and-push (push) Has been cancelled
2026-06-01 14:58:33 +09:00
gomdobi ffce1ef370 Fallback to local sessions without Redis bean 2026-06-01 13:35:32 +09:00
gomdobi 5b783fe817 Keep Redis sessions disabled outside dev 2026-06-01 13:34:15 +09:00
gomdobi 4e1d15c574 Support optional Redis sessions and heap dumps 2026-06-01 13:26:44 +09:00
gomdobi 0707f726b8 Use deployment patch for session dev scaling 2026-06-01 11:01:16 +09:00
gomdobi a6c2c631ec Fix dev session Redis deployment validation 2026-06-01 10:58:20 +09:00
gomdobi 898671cd4d Add Redis-backed sessions for dev validation 2026-06-01 10:48:43 +09:00
gomdobi 1739d607ab Increase helpdesk JVM heap to 2g
Main-Build / build-and-push (push) Has been cancelled
2026-06-01 09:48:02 +09:00
saydev dca7c687c8 Merge pull request '날짜표기수정' (#26) from feature/counting into master
Main-Build / build-and-push (push) Successful in 26s
Reviewed-on: #26
Reviewed-by: saydev <gomdobi@sayinfo.co.kr>
2026-01-08 00:38:44 +00:00
saydev 9b35c0e445 Merge pull request 'css' (#25) from feature/counting into master
Main-Build / build-and-push (push) Successful in 26s
Reviewed-on: #25
Reviewed-by: saydev <gomdobi@sayinfo.co.kr>
2026-01-07 23:45:53 +00:00
saydev 5ca0ea9c92 Merge pull request 'css cash refresh' (#24) from feature/counting into master
Main-Build / build-and-push (push) Successful in 27s
Reviewed-on: #24
Reviewed-by: saydev <gomdobi@sayinfo.co.kr>
2026-01-06 02:40:37 +00:00
saydev 441754d72c Merge pull request '처리 현황 집계 기준을 지난 한달간으로 변경하고 막대그래프 제목 아래에 연간요청현황 추가' (#23) from feature/counting into master
Main-Build / build-and-push (push) Successful in 29s
Reviewed-on: #23
Reviewed-by: saydev <gomdobi@sayinfo.co.kr>
2026-01-06 02:31:17 +00:00
12 changed files with 382 additions and 8 deletions
+7 -4
View File
@@ -20,20 +20,23 @@ ENV TZ=Asia/Seoul \
OTEL_EXPORTER_OTLP_PROTOCOL=grpc \
OTEL_RESOURCE_ATTRIBUTES="deployment.environment=${OTEL_ENV}" \
JAVA_TOOL_OPTIONS="\
-Xms1g \
-Xmx1g \
-Xms2g \
-Xmx2g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+UnlockExperimentalVMOptions \
-XX:+UseStringDeduplication \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/tmp \
-XX:HeapDumpPath=/heapdumps \
-XX:+ExitOnOutOfMemoryError \
-XX:+DisableExplicitGC \
-javaagent:/opt/opentelemetry-javaagent.jar"
# 타임존 설정
RUN ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
RUN mkdir -p /heapdumps && chown -R 1000:1000 /heapdumps
# OTEL 에이전트 복사
COPY --from=otel /opentelemetry-javaagent.jar /opt/opentelemetry-javaagent.jar
RUN chown 1000:1000 /opt/opentelemetry-javaagent.jar
@@ -46,4 +49,4 @@ COPY target/sayit-helpdesk.war /usr/local/tomcat/webapps/ROOT.war
RUN chown -R 1000:1000 /usr/local/tomcat/
EXPOSE 8080
CMD ["catalina.sh","run"]
CMD ["catalina.sh","run"]
Vendored
+43 -3
View File
@@ -81,8 +81,10 @@ spec:
def OTEL_ENV = (TARGET_ENV == 'dev') ? 'dev' : (TARGET_ENV == 'stage') ? 'stage' : 'prod'
def OTEL_SERVICE_NAME = (TARGET_ENV == 'dev') ? 'sayit-helpdesk-dev' : (TARGET_ENV == 'stage') ? 'sayit-helpdesk-stage' : 'sayit-helpdesk'
def IMAGE_TAG = "${TARGET_ENV}-${env.BUILD_NUMBER}" // dev-123 / stage-123 / prod-123
def LATEST_TAG = "latest-${TARGET_ENV}" // latest-dev / latest-stage / latest-prod
def IS_SESSION_JOB = env.JOB_NAME?.contains('session') ? 'true' : 'false'
def IMAGE_TAG_PREFIX = (IS_SESSION_JOB == 'true') ? "${TARGET_ENV}-session" : TARGET_ENV
def IMAGE_TAG = "${IMAGE_TAG_PREFIX}-${env.BUILD_NUMBER}" // dev-123 / dev-session-123
def LATEST_TAG = "latest-${IMAGE_TAG_PREFIX}" // latest-dev / latest-dev-session
timestamps {
@@ -92,6 +94,7 @@ spec:
echo "APP_NS = ${APP_NS}"
echo "OTEL_ENV = ${OTEL_ENV}"
echo "OTEL_SERVICE_NAME = ${OTEL_SERVICE_NAME}"
echo "IS_SESSION_JOB = ${IS_SESSION_JOB}"
echo "IMAGE_TAG = ${IMAGE_TAG}"
echo "LATEST_TAG = ${LATEST_TAG}"
}
@@ -146,15 +149,52 @@ spec:
container('kubectl') {
sh """
set -eux
if [ "${IS_SESSION_JOB}" = "true" ]; then
kubectl -n ${APP_NS} patch deploy ${DEPLOY} --type=merge -p '{"spec":{"replicas":0}}'
for i in \$(seq 1 150); do
POD_COUNT=\$(kubectl -n ${APP_NS} get pod -l app=${DEPLOY} --no-headers 2>/dev/null | wc -l | tr -d ' ')
[ "\${POD_COUNT}" = "0" ] && break
sleep 2
if [ "\${i}" = "150" ]; then
kubectl -n ${APP_NS} get pod -l app=${DEPLOY} -o wide
exit 1
fi
done
fi
if [ -f "k8s/${TARGET_ENV}/session-redis.yaml" ]; then
kubectl apply -f "k8s/${TARGET_ENV}/session-redis.yaml"
kubectl -n ${APP_NS} rollout status deploy/sayit-helpdesk-session-redis --timeout=120s
fi
if [ "${TARGET_ENV}" = "dev" ]; then
kubectl -n ${APP_NS} patch deploy ${DEPLOY} --type=strategic -p '{"spec":{"template":{"spec":{"containers":[{"name":"'${DEPLOY}'","env":[{"name":"SPRING_PROFILES_ACTIVE","value":"redis-session"}],"volumeMounts":[{"name":"heapdumps","mountPath":"/heapdumps"}]}],"volumes":[{"name":"heapdumps","nfs":{"server":"192.168.0.120","path":"/volume2/NFS/helpdesk-dev/heapdumps"}}]}}}}'
elif [ "${TARGET_ENV}" = "prod" ]; then
kubectl -n ${APP_NS} patch deploy ${DEPLOY} --type=strategic -p '{"spec":{"template":{"spec":{"containers":[{"name":"'${DEPLOY}'","env":[{"name":"SPRING_PROFILES_ACTIVE","value":"redis-session"}]}]}}}}'
fi
kubectl -n ${APP_NS} set image deploy/${DEPLOY} ${DEPLOY}=${REG}/${IMAGE}:${IMAGE_TAG}
if [ "${IS_SESSION_JOB}" = "true" ]; then
kubectl -n ${APP_NS} patch deploy ${DEPLOY} --type=merge -p '{"spec":{"replicas":1}}'
fi
kubectl -n ${APP_NS} rollout status deploy/${DEPLOY} --timeout=300s
DEPLOY_IMAGE=\$(kubectl -n ${APP_NS} get deploy ${DEPLOY} -o jsonpath='{.spec.template.spec.containers[0].image}')
test "\${DEPLOY_IMAGE}" = "${REG}/${IMAGE}:${IMAGE_TAG}"
kubectl -n ${APP_NS} get deploy ${DEPLOY} -o wide
kubectl -n ${APP_NS} get pods -l app=${DEPLOY} -o wide
kubectl -n ${APP_NS} get pod -l app=${DEPLOY} -o jsonpath='{.items[*].spec.containers[*].image}'; echo
if [ "${IS_SESSION_JOB}" = "true" ]; then
POD_IMAGES=\$(kubectl -n ${APP_NS} get pod -l app=${DEPLOY} -o jsonpath='{.items[*].spec.containers[*].image}')
test "\${POD_IMAGES}" = "${REG}/${IMAGE}:${IMAGE_TAG}"
fi
"""
}
}
}
}
}
}
+67
View File
@@ -0,0 +1,67 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: sayit-helpdesk-session-redis
namespace: sayit-helpdesk-dev
labels:
app: sayit-helpdesk-session-redis
spec:
replicas: 1
selector:
matchLabels:
app: sayit-helpdesk-session-redis
template:
metadata:
labels:
app: sayit-helpdesk-session-redis
spec:
containers:
- name: redis
image: redis:7.2-alpine
imagePullPolicy: IfNotPresent
args:
- redis-server
- --save
- ""
- --appendonly
- "no"
ports:
- name: redis
containerPort: 6379
readinessProbe:
tcpSocket:
port: redis
initialDelaySeconds: 3
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
livenessProbe:
tcpSocket:
port: redis
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 500m
memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
name: sayit-helpdesk-session-redis
namespace: sayit-helpdesk-dev
labels:
app: sayit-helpdesk-session-redis
spec:
type: ClusterIP
selector:
app: sayit-helpdesk-session-redis
ports:
- name: redis
port: 6379
targetPort: redis
+67
View File
@@ -0,0 +1,67 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: sayit-helpdesk-session-redis
namespace: sayit-helpdesk
labels:
app: sayit-helpdesk-session-redis
spec:
replicas: 1
selector:
matchLabels:
app: sayit-helpdesk-session-redis
template:
metadata:
labels:
app: sayit-helpdesk-session-redis
spec:
containers:
- name: redis
image: redis:7.2-alpine
imagePullPolicy: IfNotPresent
args:
- redis-server
- --save
- ""
- --appendonly
- "no"
ports:
- name: redis
containerPort: 6379
readinessProbe:
tcpSocket:
port: redis
initialDelaySeconds: 3
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
livenessProbe:
tcpSocket:
port: redis
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 500m
memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
name: sayit-helpdesk-session-redis
namespace: sayit-helpdesk
labels:
app: sayit-helpdesk-session-redis
spec:
type: ClusterIP
selector:
app: sayit-helpdesk-session-redis
ports:
- name: redis
port: 6379
targetPort: redis
+21 -1
View File
@@ -20,6 +20,7 @@
<log4j2.version>2.1</log4j2.version>
<jackson.version>1.9.13</jackson.version>
<httpcomponents.version>4.5.2</httpcomponents.version>
<spring.session.version>1.1.1.RELEASE</spring.session.version>
</properties>
<!--<repositories>-->
@@ -130,6 +131,25 @@
<artifactId>egovframework.rte.fdl.property</artifactId>
<version>${egovframework.rte.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>${spring.session.version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${spring.maven.artifact.version}</version>
</dependency>
<!-- Spring Framework -->
<!-- <dependency>-->
<!-- <groupId>org.springframework</groupId>-->
@@ -801,4 +821,4 @@
</build>
</profile>
</profiles>
</project>
</project>
@@ -0,0 +1,99 @@
package egovframework.main.web;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
public class OptionalSpringSessionFilter implements Filter {
private static final String PROPERTIES_PATH = "egovframework/egovProps/globals.properties";
private static final String ENABLED_KEY = "Session.Redis.Enabled";
private static final String TARGET_BEAN_NAME = "springSessionRepositoryFilter";
private ServletContext servletContext;
private boolean enabled;
private volatile Filter delegate;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.servletContext = filterConfig.getServletContext();
this.enabled = Boolean.parseBoolean(loadProperty(ENABLED_KEY, "false"));
if (this.enabled) {
this.servletContext.log("Spring Session Redis filter is enabled.");
} else {
this.servletContext.log("Spring Session Redis filter is disabled.");
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (!this.enabled) {
chain.doFilter(request, response);
return;
}
Filter current = getDelegate();
if (current == null) {
chain.doFilter(request, response);
return;
}
current.doFilter(request, response, chain);
}
@Override
public void destroy() {
this.delegate = null;
}
private Filter getDelegate() throws ServletException {
Filter current = this.delegate;
if (current != null) {
return current;
}
synchronized (this) {
current = this.delegate;
if (current == null) {
WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(this.servletContext);
if (!context.containsBean(TARGET_BEAN_NAME)) {
this.enabled = false;
this.servletContext.log("Spring Session Redis filter bean was not found. Falling back to local servlet sessions.");
return null;
}
current = context.getBean(TARGET_BEAN_NAME, Filter.class);
this.delegate = current;
}
return current;
}
}
private String loadProperty(String key, String defaultValue) {
InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream(PROPERTIES_PATH);
if (input == null) {
return defaultValue;
}
try {
Properties properties = new Properties();
properties.load(input);
return properties.getProperty(key, defaultValue).trim();
} catch (IOException e) {
this.servletContext.log("Failed to load " + PROPERTIES_PATH + ". Using default for " + key + ".", e);
return defaultValue;
} finally {
try {
input.close();
} catch (IOException ignored) {
}
}
}
}
@@ -152,3 +152,12 @@ ldap.url =ldap://localhost:10389
ldap.rootDn =c=kr
ldap.username =uid=admin,ou=system
ldap.password =secret
# Spring Session Redis
Session.Redis.Enabled=true
Session.Redis.Host=sayit-helpdesk-session-redis
Session.Redis.Port=6379
Session.Redis.Database=0
Session.Redis.Timeout=2000
Session.Redis.Namespace=sayit-helpdesk-dev:session
Session.Redis.MaxInactiveIntervalInSeconds=36000
@@ -150,3 +150,12 @@ ldap.url =ldap://localhost:10389
ldap.rootDn =c=kr
ldap.username =uid=admin,ou=system
ldap.password =secret
# Spring Session Redis
Session.Redis.Enabled=true
Session.Redis.Host=sayit-helpdesk-session-redis
Session.Redis.Port=6379
Session.Redis.Database=0
Session.Redis.Timeout=2000
Session.Redis.Namespace=sayit-helpdesk:session
Session.Redis.MaxInactiveIntervalInSeconds=36000
@@ -145,3 +145,12 @@ ldap.url =ldap://localhost:10389
ldap.rootDn =c=kr
ldap.username =uid=admin,ou=system
ldap.password =secret
# Spring Session Redis
Session.Redis.Enabled=false
Session.Redis.Host=sayit-helpdesk-session-redis
Session.Redis.Port=6379
Session.Redis.Database=0
Session.Redis.Timeout=2000
Session.Redis.Namespace=sayit-helpdesk-stage:session
Session.Redis.MaxInactiveIntervalInSeconds=36000
@@ -152,3 +152,6 @@ ldap.url =ldap://localhost:10389
ldap.rootDn =c=kr
ldap.username =uid=admin,ou=system
ldap.password =secret
# Spring Session Redis
Session.Redis.Enabled=false
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<beans profile="redis-session">
<context:annotation-config/>
<util:constant id="redisFlushModeOnSave"
static-field="org.springframework.session.data.redis.RedisFlushMode.ON_SAVE"/>
<util:constant id="configureRedisActionNoOp"
static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/>
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="${Session.Redis.MaxInactiveIntervalInSeconds}"/>
<property name="redisNamespace" value="${Session.Redis.Namespace}"/>
<property name="redisFlushMode" ref="redisFlushModeOnSave"/>
<property name="configureRedisAction" ref="configureRedisActionNoOp"/>
</bean>
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${Session.Redis.Host}"/>
<property name="port" value="${Session.Redis.Port}"/>
<property name="database" value="${Session.Redis.Database}"/>
<property name="timeout" value="${Session.Redis.Timeout}"/>
<property name="usePool" value="true"/>
</bean>
<bean id="cookieSerializer" class="org.springframework.session.web.http.DefaultCookieSerializer">
<property name="cookieName" value="JSESSIONID"/>
<property name="cookiePath" value="/"/>
<property name="useHttpOnlyCookie" value="true"/>
</bean>
</beans>
</beans>
+8
View File
@@ -1,6 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:jsp="http://java.sun.com/xml/ns/javaee/jsp" xmlns:web="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>Default</display-name>
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>egovframework.main.web.OptionalSpringSessionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>