The IO pin has approximately 10 to 50 ohms of series resistance, depending largely on the chip, unit, and temperature.
Diode forward voltage vs. current is not a constant; it's a (nearly) logarithmic function, which can be approximated as a constant, but this approximation is indeed crude. Compared to the linear function of a resistor, the logarithm surely looks like a constant! The exact curve is in the datasheet.
Additionally, unit-to-unit variation in Vf of LEDs is large. You ruled this out by measuring the particular unit, but if you do a design, you need to verify that the extremes given in the datasheet are OK for you.
Increasing LED temperature lowers the Vf, causing more current to flow, causing even more heating.
All of this means, you can't regulate the LED current accurately by the voltage source + series resistor approach. The more voltage you drop over the resistor, the more accurate it gets, but if you need any kind of accurate current, you should use some kind of constant current driver circuit, there are many options for that.
For simple indication purposes, just design the LED current to be considerably less than the maximum rating, so that variations from the ideal won't cause damage. Modern high-intensity LEDs can be seen well, even in bright conditions, at 5 mA, and they can handle usually 30mA, so you have a lot of leeway.
You can do the worst case analysis if you want, for example:
1) The Arduino board's 5V regulator likely has some +/- 4% worst case tolerance, so Vsupply may be up to 5.0 + 4%;
2) Your 220ohm series resistor might have 5% tolerance, so it may be 220 ohm - 5%;
3) Look up the minimum possible Vf from the LED datasheet. Note that this is likely given at junction temperature of 25 degC or something like that!
4) Calculate the LED current in these conditions;
5) Calculate LED power dissipation P = Vf * I
6) Calculate LED junction temperature: Tj = Tambient + P * Rthj-a (from the LED datasheet)
7) Calculate the Vf at that temperature: look up if the datasheet lists the thermal coefficient V/degC
8) Iteratively go back to step 5. If the numbers change significantly, you may have a thermal runaway.