Fix deployment timestamp always showing 'less than a minute ago' in k8s
Previously, the deployment timestamp was set to DateTime.utc_now() when the application started, which meant it would always show as recent in k8s environments where pods are recreated on each deployment. This commit: - Updates Release.init() to check for DEPLOYED_AT environment variable - Falls back to current time if env var is not set (backwards compatible) - Adds proper ISO8601 timestamp parsing with error handling - Updates GitHub Actions deploy workflow to set DEPLOYED_AT during deployment - Creates k8s patch file for future manual deployments The deployment timestamp will now persist across pod restarts in k8s, showing the actual deployment time rather than pod startup time. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
ece5c82e99
commit
7e4bbbaff5
6 changed files with 77 additions and 23 deletions
27
.github/workflows/deploy.yml
vendored
27
.github/workflows/deploy.yml
vendored
|
|
@ -74,7 +74,32 @@ jobs:
|
||||||
|
|
||||||
- name: Deploy to K3s
|
- name: Deploy to K3s
|
||||||
run: |
|
run: |
|
||||||
|
# Get current timestamp in ISO8601 format
|
||||||
|
DEPLOYED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
|
||||||
|
# Create a temporary patch file with the deployment timestamp
|
||||||
|
cat > /tmp/deployed-at-patch.yaml <<EOF
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: aprs
|
||||||
|
namespace: aprs
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: aprs
|
||||||
|
env:
|
||||||
|
- name: DEPLOYED_AT
|
||||||
|
value: "$DEPLOYED_AT"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Apply the patch to set the deployment timestamp
|
||||||
|
kubectl patch statefulset aprs -n aprs --patch-file=/tmp/deployed-at-patch.yaml
|
||||||
|
|
||||||
|
# Update the image
|
||||||
kubectl set image statefulset/aprs aprs=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest -n aprs
|
kubectl set image statefulset/aprs aprs=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest -n aprs
|
||||||
|
|
||||||
# Force a rollout to ensure latest image is pulled
|
# Force a rollout to ensure latest image is pulled
|
||||||
kubectl rollout restart statefulset/aprs -n aprs
|
kubectl rollout restart statefulset/aprs -n aprs
|
||||||
echo "Deployment initiated - not waiting for rollout to complete"
|
echo "Deployment initiated with timestamp $DEPLOYED_AT - not waiting for rollout to complete"
|
||||||
|
|
|
||||||
|
|
@ -57,9 +57,8 @@ ENV LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=en_US.UTF-8
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Create deployment timestamp before switching users
|
# Ensure proper ownership
|
||||||
RUN date -u +"%Y-%m-%dT%H:%M:%SZ" > /app/deployed_at.txt && \
|
RUN chown -R elixir:root /app
|
||||||
chown elixir:root /app/deployed_at.txt
|
|
||||||
|
|
||||||
COPY --from=builder --chown=elixir:root /app/_build/prod/rel/aprsme ./
|
COPY --from=builder --chown=elixir:root /app/_build/prod/rel/aprsme ./
|
||||||
|
|
||||||
|
|
|
||||||
13
k8s/statefulset-deployed-at-patch.yaml
Normal file
13
k8s/statefulset-deployed-at-patch.yaml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: aprs
|
||||||
|
namespace: aprs
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: aprs
|
||||||
|
env:
|
||||||
|
- name: DEPLOYED_AT
|
||||||
|
value: "TIMESTAMP_PLACEHOLDER"
|
||||||
|
|
@ -9,6 +9,9 @@ defmodule Aprsme.Application do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def start(_type, _args) do
|
def start(_type, _args) do
|
||||||
|
# Initialize deployment timestamp
|
||||||
|
Aprsme.Release.init()
|
||||||
|
|
||||||
# Run migrations on startup
|
# Run migrations on startup
|
||||||
migrate()
|
migrate()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,10 +75,29 @@ defmodule Aprsme.Release do
|
||||||
This is called during application startup.
|
This is called during application startup.
|
||||||
"""
|
"""
|
||||||
def init do
|
def init do
|
||||||
# Read deployment timestamp from file
|
# Check if deployment timestamp is provided via environment variable
|
||||||
deployed_at = read_deployment_timestamp()
|
# This allows k8s deployments to set a persistent timestamp
|
||||||
|
deployed_at =
|
||||||
|
case System.get_env("DEPLOYED_AT") do
|
||||||
|
nil ->
|
||||||
|
# Fallback to current time for local development or non-k8s deployments
|
||||||
|
DateTime.utc_now()
|
||||||
|
|
||||||
# Add to application config
|
timestamp_str ->
|
||||||
|
# Parse the ISO8601 timestamp from environment variable
|
||||||
|
case DateTime.from_iso8601(timestamp_str) do
|
||||||
|
{:ok, datetime, _offset} ->
|
||||||
|
datetime
|
||||||
|
|
||||||
|
{:error, _} ->
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
Logger.warning("Invalid DEPLOYED_AT timestamp: #{timestamp_str}, using current time")
|
||||||
|
DateTime.utc_now()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Store in application config
|
||||||
Application.put_env(:aprsme, :deployed_at, deployed_at)
|
Application.put_env(:aprsme, :deployed_at, deployed_at)
|
||||||
|
|
||||||
deployed_at
|
deployed_at
|
||||||
|
|
@ -88,7 +107,16 @@ defmodule Aprsme.Release do
|
||||||
Get the deployment timestamp.
|
Get the deployment timestamp.
|
||||||
"""
|
"""
|
||||||
def deployed_at do
|
def deployed_at do
|
||||||
Application.get_env(:aprsme, :deployed_at) || DateTime.utc_now()
|
case Application.get_env(:aprsme, :deployed_at) do
|
||||||
|
nil ->
|
||||||
|
# If not initialized yet, initialize now
|
||||||
|
timestamp = DateTime.utc_now()
|
||||||
|
Application.put_env(:aprsme, :deployed_at, timestamp)
|
||||||
|
timestamp
|
||||||
|
|
||||||
|
timestamp ->
|
||||||
|
timestamp
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp run_migrations_with_timeout(timeout) do
|
defp run_migrations_with_timeout(timeout) do
|
||||||
|
|
@ -113,18 +141,4 @@ defmodule Aprsme.Release do
|
||||||
timeout: timeout
|
timeout: timeout
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp read_deployment_timestamp do
|
|
||||||
case File.read("/app/deployed_at.txt") do
|
|
||||||
{:ok, timestamp} ->
|
|
||||||
case DateTime.from_iso8601(String.trim(timestamp)) do
|
|
||||||
{:ok, datetime, _} -> datetime
|
|
||||||
_ -> DateTime.utc_now()
|
|
||||||
end
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
# Fallback to current time if file doesn't exist
|
|
||||||
DateTime.utc_now()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue